Trove API Reference
REST API 엔드포인트 문서 | Base URL: http://localhost:8080/api/v1
Table of Contents
- Authentication
- Error Response Format
- System
- Auth (인증)
- Users (사용자)
- Trails (트레일)
- Check-ins (체크인)
- Activities (활동)
- Achievements (업적)
- Missions (미션)
- Gamification (게이미피케이션)
- Leaderboard (리더보드)
- Stamps (스탬프)
- Checkpoints (체크포인트)
- Segments (구간)
- Feed (피드)
- Posts (게시물)
- Kudos (응원)
- Events (이벤트)
- Notifications (알림)
- Upload (업로드)
- Nearby (주변)
- Weather (날씨)
- Recommendations (추천)
- GPX
- Knowledge Graph (지식 그래프)
- Statistics
Authentication
Most endpoints require JWT Bearer token authentication.
Authorization: Bearer <access_token>- Obtain tokens via
POST /api/v1/auth/loginorPOST /api/v1/auth/register - Access tokens expire after a configured duration
- Use
POST /api/v1/auth/refreshwith a refresh token to get new tokens - Endpoints marked with Auth: Required need a valid Bearer token
- Endpoints marked with Auth: Optional accept a token for personalized results
- Endpoints marked with Auth: None are publicly accessible
Error Response Format
All errors follow a consistent envelope:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "request validation failed",
"details": { ... }
}
}| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | BAD_REQUEST / VALIDATION_ERROR | Invalid request parameters |
| 401 | UNAUTHORIZED | Missing or invalid authentication |
| 403 | FORBIDDEN | Insufficient permissions |
| 404 | NOT_FOUND | Resource not found |
| 409 | CONFLICT | Resource already exists |
| 429 | RATE_LIMITED | Too many requests |
| 500 | INTERNAL_SERVER_ERROR | Server-side error |
Endpoints
System
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /health | None | Root health check endpoint |
GET | /api/v1/health | None | API v1 health check endpoint |
`GET /health`
Description: Root health check endpoint
Auth Required: No
Example:
curl -X GET "http://localhost:8080/api/v1/health"`GET /api/v1/health`
Description: API v1 health check endpoint
Auth Required: No
Example:
curl -X GET "http://localhost:8080/api/v1/health"Auth (인증)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/auth/register | None | Register |
POST | /api/v1/auth/login | None | Login |
POST | /api/v1/auth/refresh | None | Refresh Token |
POST | /api/v1/auth/logout | Required | Logout |
POST | /api/v1/auth/change-password | Required | Change Password |
`POST /api/v1/auth/register`
Description: Register
Auth Required: No
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
email | string | Yes | Valid email; Max: 255 |
username | string | Yes | Min: 3; Max: 50; Alphanumeric + underscore |
password | string | Yes | Min: 8; Max: 128 |
display_name | string | Yes | Min: 1; Max: 100 |
preferred_language | string | No | One of: ko, en, ja, zh_cn, zh_tw |
Response (201):
{
"success": true,
"data": { /* AuthResponse */ }
}Response data fields (AuthResponse):
| Field | Type | Description |
|---|---|---|
user | UserResponse | |
tokens | TokenPair |
Example:
curl -X POST "http://localhost:8080/api/v1/auth/register" \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "username": "Sample Name", "password": "password123", "display_name": "Sample Name", "preferred_language": "ko"}'`POST /api/v1/auth/login`
Description: Login
Auth Required: No
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
email | string | Yes | Valid email |
password | string | Yes |
Response (200):
{
"success": true,
"data": { /* AuthResponse */ }
}Response data fields (AuthResponse):
| Field | Type | Description |
|---|---|---|
user | UserResponse | |
tokens | TokenPair |
Example:
curl -X POST "http://localhost:8080/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "password123"}'`POST /api/v1/auth/refresh`
Description: Refresh Token
Auth Required: No
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
refresh_token | string | Yes |
Response (200):
{
"success": true,
"data": { /* TokenPair */ }
}Response data fields (TokenPair):
| Field | Type | Description |
|---|---|---|
access_token | string | |
refresh_token | string | |
expires_at | integer | Unix timestamp |
Example:
curl -X POST "http://localhost:8080/api/v1/auth/refresh" \
-H "Content-Type: application/json" \
-d '{"refresh_token": "eyJ..."}'`POST /api/v1/auth/logout`
Description: Logout
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/auth/logout" \
-H "Authorization: Bearer $TOKEN"`POST /api/v1/auth/change-password`
Description: Change Password
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
current_password | string | Yes | |
new_password | string | Yes | Min: 8; Max: 128 |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/auth/change-password" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"current_password": "password123", "new_password": "password123"}'Users (사용자)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/users/me | Required | Get Me |
GET | /api/v1/users/me/stats | Required | returns the current user's stats |
PATCH | /api/v1/users/me | Required | Update Profile |
GET | /api/v1/users/:id | None | Get Profile |
GET | /api/v1/users/:id/profile | None | returns public profile with stats |
GET | /api/v1/users/:id/activities | None | list user's public activities |
GET | /api/v1/users/:id/achievements | None | list user's earned badges |
GET | /api/v1/users/:id/stamps | None | list user's check-in stamps |
POST | /api/v1/users/:id/follow | Required | Follow User |
DELETE | /api/v1/users/:id/follow | Required | Unfollow User |
GET | /api/v1/users/me/local-legends | Required | Returns trails where the authenticated user is the local legend |
`GET /api/v1/users/me`
Description: Get Me
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { /* UserProfileResponse */ }
}Response data fields (UserProfileResponse):
| Field | Type | Description |
|---|---|---|
follower_count | integer | |
following_count | integer | |
achievement_count | integer |
Example:
curl -X GET "http://localhost:8080/api/v1/users/me" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/users/me/stats`
Description: returns the current user's stats
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { /* UserStatsResponse */ }
}Response data fields (UserStatsResponse):
| Field | Type | Description |
|---|---|---|
total_checkins | integer | |
total_distance_km | number | |
total_activities | integer | |
total_xp | integer | |
level | integer | |
current_streak | integer | |
longest_streak | integer | |
trails_completed | integer | |
achievements_earned | integer | |
follower_count | integer | |
following_count | integer |
Example:
curl -X GET "http://localhost:8080/api/v1/users/me/stats" \
-H "Authorization: Bearer $TOKEN"`PATCH /api/v1/users/me`
Description: Update Profile
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
display_name | string? | No | Min: 1; Max: 100 |
bio | string? | No | Max: 500 |
avatar_url | string? | No | Valid URL |
preferred_language | string? | No | One of: ko, en, ja, zh_cn, zh_tw |
fitness_level | string? | No | One of: beginner, intermediate, advanced, expert |
interests | string[] | No | Max: 20 |
Response (200):
{
"success": true,
"data": { /* UserResponse */ }
}Response data fields (UserResponse):
| Field | Type | Description |
|---|---|---|
id | uuid | |
email | string | |
username | string | |
display_name | string | |
avatar_url | string? | |
bio | string? | |
preferred_language | string | |
fitness_level | string | |
interests | string[] | |
total_distance_km | number | |
total_checkins | integer | |
level | integer | |
xp_points | integer | |
current_streak | integer | |
longest_streak | integer | |
is_verified | boolean | |
is_premium | boolean | |
created_at | datetime |
Example:
curl -X PATCH "http://localhost:8080/api/v1/users/me" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"display_name": "Sample Name", "bio": "value", "avatar_url": "https://example.com/image.jpg", "preferred_language": "ko", "fitness_level": "beginner", "interests": ["item1", "item2"]}'`GET /api/v1/users/:id`
Description: Get Profile
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/users/{id}"`GET /api/v1/users/:id/profile`
Description: returns public profile with stats
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/users/{id}/profile"`GET /api/v1/users/:id/activities`
Description: list user's public activities
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/users/{id}/activities"`GET /api/v1/users/:id/achievements`
Description: list user's earned badges
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/users/{id}/achievements"`GET /api/v1/users/:id/stamps`
Description: list user's check-in stamps
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/users/{id}/stamps"`POST /api/v1/users/:id/follow`
Description: Follow User
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/users/{id}/follow" \
-H "Authorization: Bearer $TOKEN"`DELETE /api/v1/users/:id/follow`
Description: Unfollow User
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X DELETE "http://localhost:8080/api/v1/users/{id}/follow" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/users/me/local-legends`
Description: Returns trails where the authenticated user is the local legend
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/users/me/local-legends" \
-H "Authorization: Bearer $TOKEN"Trails (트레일)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/trails | None | Search |
GET | /api/v1/trails/nearby | None | Find Nearby |
GET | /api/v1/trails/:id | None | Get By ID |
POST | /api/v1/trails | Required | Create |
GET | /api/v1/trails/:id/checkpoints | None | Get Checkpoints |
GET | /api/v1/trails/:id/stages | None | Get Stages |
GET | /api/v1/trails/:id/weather | None | Get Weather |
GET | /api/v1/trails/quick-start | None | Get Quick Start Routes |
GET | /api/v1/trails/seasonal | None | Get Seasonal Recommendations |
GET | /api/v1/trails/time-aware | None | Get Time Aware Recommendations |
GET | /api/v1/trails/:id/reviews | None | Get Trail Reviews |
POST | /api/v1/trails/:id/reviews | Required | Create Review |
POST | /api/v1/trails/:id/complete | Required | verify trail completion |
GET | /api/v1/trails/:id/stories | None | Get By Trail ID |
GET | /api/v1/trails/:id/stories/nearby | None | Find Nearby |
GET | /api/v1/trails/:id/segments | None | Returns all segments (구간) for a given trail |
GET | /api/v1/trails/:id/local-legend | None | Returns the user with the most check-ins on this trail (로컬 레전드) |
GET | /api/v1/trails/:id/packing-list | None | Returns a weather-based packing checklist for the specified trail |
`GET /api/v1/trails`
Description: Search
Auth Required: No
Query Parameters:
| Field | Type | Required | Validation |
|---|---|---|---|
q | string | No | Max: 255 |
region | string | No | Max: 100 |
city | string | No | Max: 100 |
difficulty | string | No | One of: easy, moderate, hard, expert |
min_distance | number? | No | >= 0 |
max_distance | number? | No | >= 0 |
seasons | string[] | No | One of: spring, summer, autumn, winter |
sort_by | string | No | One of: rating, distance, name, created_at |
sort_order | string | No | One of: asc, desc |
page | integer | No | Min: 1 |
page_size | integer | No | Min: 1; Max: 100 |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails"`GET /api/v1/trails/nearby`
Description: Find Nearby
Auth Required: No
Query Parameters:
| Field | Type | Required | Validation |
|---|---|---|---|
latitude | number | Yes | Valid latitude |
longitude | number | Yes | Valid longitude |
radius_km | number | No | > 0; Max: 100 |
limit | integer | No | Min: 1; Max: 50 |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/nearby"`GET /api/v1/trails/:id`
Description: Get By ID
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/{id}"`POST /api/v1/trails`
Description: Create
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
name_ko | string | Yes | Max: 255 |
name_en | string? | No | Max: 255 |
description_ko | string? | No | |
description_en | string? | No | |
trail_system | string? | No | Max: 100 |
managing_organization | string? | No | Max: 200 |
region | string? | No | Max: 100 |
city | string? | No | Max: 100 |
country | string | No | Max: 10 |
total_distance_km | number? | No | > 0 |
estimated_duration_minutes | integer? | No | > 0 |
elevation_gain_m | integer? | No | |
elevation_loss_m | integer? | No | |
max_elevation_m | integer? | No | |
min_elevation_m | integer? | No | |
difficulty | string | Yes | One of: easy, moderate, hard, expert |
surface_type | string | Yes | One of: paved, gravel, dirt, rock, mixed, sand, boardwalk |
is_wheelchair_accessible | boolean | No | |
is_pet_friendly | boolean | No | |
is_loop | boolean | No | |
best_seasons | string[] | No | One of: spring, summer, autumn, winter |
terrain_tags | string[] | No | Max: 20 |
thumbnail_url | string? | No | Valid URL |
Response (201):
{
"success": true,
"data": { /* TrailResponse */ }
}Response data fields (TrailResponse):
| Field | Type | Description |
|---|---|---|
id | uuid | |
name_ko | string | |
name_en | string? | |
description_ko | string? | |
description_en | string? | |
trail_system | string? | |
region | string? | |
city | string? | |
country | string | |
start_latitude | number? | |
start_longitude | number? | |
total_distance_km | number? | |
estimated_duration_minutes | integer? | |
elevation_gain_m | integer? | |
elevation_loss_m | integer? | |
difficulty | string | |
surface_type | string | |
is_wheelchair_accessible | boolean | |
is_pet_friendly | boolean | |
is_loop | boolean | |
route_shape | string? | Added 2026-09-11. Both GET /trails/:id AND GET /trails (list) — unlike data_source/network_segments/segment_ids above, this is not detail-only. Verbatim trails.route_shape (S2c, migration 20260814035733): one of loop / out_and_back / point_to_point / network. Omitted when the row is not yet derived (NULL) or the derivation concluded unknown (판정 입력 없음) — both collapse to "no key", never surfaced as the literal string "unknown". is_loop above is unchanged and unrelated to this field's presence. |
best_seasons | string[] | |
terrain_tags | string[] | |
thumbnail_url | string? | |
rating_avg | number | |
rating_count | integer | |
completion_count | integer | |
created_at | datetime | |
data_source | string? | Added 2026-09-10 (ACHASAN-NETWORK-COURSE-COMBINE). GET /trails/:id only — list endpoints omit the key. Internal ingestion code (e.g. kfs_aggregated_v1); clients must branch on it, never display it. |
network_segments | [[[number,number]]]? | Added 2026-09-10. GET /trails/:id only, present only when route_status="preparing". RAW 망(network) geometry — every sub-line, before Stage A hides it — same [[ [lon,lat], ... ], ...] shape as route_segments. NOT the render truth (there is no derived course yet); route_segments/route_coordinates stay empty for these rows, unchanged. |
network_part_count | integer? | Added 2026-09-10. len(network_segments), alongside it (same presence rule). |
segment_ids | string[]? | Added 2026-09-10. GET /trails/:id only, present only when the row's provenance_notes.segment_ids JSON array is set (curation lineage). Omitted, never an empty array, when absent. |
Example:
curl -X POST "http://localhost:8080/api/v1/trails" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name_ko": "Sample Name", "name_en": "Sample Name", "description_ko": "Sample text content", "description_en": "Sample text content", "trail_system": "value", "managing_organization": "value", "region": "value", "city": "value", "country": "value", "total_distance_km": 5.2, "estimated_duration_minutes": 60, "elevation_gain_m": 1, "elevation_loss_m": 1, "max_elevation_m": 20, "min_elevation_m": 1, "difficulty": "easy", "surface_type": "paved", "is_wheelchair_accessible": false, "is_pet_friendly": false, "is_loop": false, "best_seasons": ["item1", "item2"], "terrain_tags": ["item1", "item2"], "thumbnail_url": "https://example.com/image.jpg"}'`GET /api/v1/trails/:id/checkpoints`
Description: Get Checkpoints
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/{id}/checkpoints"`GET /api/v1/trails/:id/stages`
Description: Get Stages
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/{id}/stages"`GET /api/v1/trails/:id/weather`
Description: Get Weather
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/{id}/weather"`GET /api/v1/trails/quick-start`
Description: Get Quick Start Routes
Auth Required: No
Query Parameters:
| Field | Type | Required | Validation |
|---|---|---|---|
latitude | number | Yes | Valid latitude |
longitude | number | Yes | Valid longitude |
time_minutes | integer | Yes | Min: 15; Max: 480 |
activity_type | string | No | One of: hiking, walking, running, cycling, trail_running |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/quick-start"`GET /api/v1/trails/seasonal`
Description: Get Seasonal Recommendations
Auth Required: No
Query Parameters:
| Field | Type | Required | Validation |
|---|---|---|---|
latitude | number | Yes | Valid latitude |
longitude | number | Yes | Valid longitude |
radius_km | number | No | > 0; Max: 100 |
limit | integer | No | Min: 1; Max: 50 |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/seasonal"`GET /api/v1/trails/time-aware`
Description: Get Time Aware Recommendations
Auth Required: No
Query Parameters:
| Field | Type | Required | Validation |
|---|---|---|---|
latitude | number | Yes | Valid latitude |
longitude | number | Yes | Valid longitude |
radius_km | number | No | > 0; Max: 100 |
limit | integer | No | Min: 1; Max: 50 |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/time-aware"`GET /api/v1/trails/:id/reviews`
Description: Get Trail Reviews
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/{id}/reviews"`POST /api/v1/trails/:id/reviews`
Description: Create Review
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
rating | integer | Yes | Min: 1; Max: 5 |
difficulty_rating | integer? | No | Min: 1; Max: 5 |
scenery_rating | integer? | No | Min: 1; Max: 5 |
review_text | string? | No | Max: 2000 |
photos | string[] | No | Max: 5 |
visited_date | string? | No | |
season | string? | No | One of: spring, summer, autumn, winter |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/trails/{id}/reviews" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"rating": 5, "difficulty_rating": 5, "scenery_rating": 5, "review_text": "Sample text content", "photos": ["item1", "item2"], "visited_date": "2026-03-30", "season": "spring"}'`POST /api/v1/trails/:id/complete`
Description: verify trail completion
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
activity_id | string? | No |
Response (200):
{
"success": true,
"data": { ... }
}Response data fields — 보상 정책 (TrailCompletionResponse, 2026-09-10 신설 키 3):
| Field | Type | Description | |
|---|---|---|---|
xp_withheld_reason | string? | 완주는 인정됐는데 XP 만 판정으로 나가지 않은 사유. 닫힌 어휘 recompletion_cooldown \ | daily_cap, 보류 없으면 null. |
xp_ratio | number | 정가 100 에 곱해진 비율. 첫 완주 1.0 · 쿨다운(30일) 지난 재완주 0.5 · 보류 0.0. xp_earned = 100 × xp_ratio 가 항상 성립. | |
recompletion_eligible_at | string? (RFC3339) | xp_withheld_reason == "recompletion_cooldown" 일 때만 실린다. 이 완주가 그때 지급된다는 뜻이 아니다 — 보류는 종단이고 소급 지급이 없다. "그때 다시 걸으면 받는다"의 시각. |
정책 요약 (결정서 docs/data/REWARD_POLICY_DECISION_2026-09-10.md §3):
- 같은 트레일 첫 완주 = 100(전액).
- 재완주: 직전 *실지급*(
amount > 0) 뒤 30일 안이면 0 +recompletion_cooldown, 30일 지나면 50%(정수 내림). 세 번째부터도 같다. - 하루(KST) 상한 300 — 이 완주 라우트에만 적용된다. 종주 시트 완성(같은
source='trail_complete', 5000)은 상한 밖이다: 상한은 source 가 아니라 원장의description("trail complete")으로 경로를 식별한다. - 보류는 종단이다. 완주 사실·스탬프·업적·집계는 그대로 나가고 XP 만 0 이다.
Example:
curl -X POST "http://localhost:8080/api/v1/trails/{id}/complete" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"activity_id": "value"}'`GET /api/v1/trails/:id/stories`
Description: Get By Trail ID
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/{id}/stories"`GET /api/v1/trails/:id/stories/nearby`
Description: Find Nearby
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/{id}/stories/nearby"`GET /api/v1/trails/:id/segments`
Description: Returns all segments (구간) for a given trail
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/{id}/segments"`GET /api/v1/trails/:id/local-legend`
Description: Returns the user with the most check-ins on this trail (로컬 레전드)
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/{id}/local-legend"`GET /api/v1/trails/:id/packing-list`
Description: Returns a weather-based packing checklist for the specified trail
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/trails/{id}/packing-list"Check-ins (체크인)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/checkins | Required | Create |
POST | /api/v1/checkins/photo-verify | Required | verify a photo's EXIF GPS against a checkpoint. Form fields: - file: the ima... |
GET | /api/v1/checkins/me | Required | Get My Checkins |
GET | /api/v1/checkins/:id | Required | get single check-in detail |
GET | /api/v1/checkins/:id/verify | Required | verify check-in status |
`POST /api/v1/checkins`
Description: Create
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
checkpoint_id | uuid | Yes | |
verification_method | string | Yes | One of: gps, qr, photo, manual |
latitude | number | Yes | Valid latitude |
longitude | number | Yes | Valid longitude |
accuracy_meters | number? | No | >= 0 |
qr_code_value | string? | No | |
photo_url | string? | No | Valid URL |
photo_metadata | *PhotoVerificationMetadata | No | |
note | string? | No | Max: 500 |
Response (201):
{
"success": true,
"data": { /* CheckinResultResponse */ }
}Response data fields (CheckinResultResponse):
| Field | Type | Description |
|---|---|---|
checkin | CheckinResponse | |
xp_earned | integer | |
transport_mode | string | |
new_achievements | []AchievementEarned | |
streak_info | StreakInfo | |
level_progress | LevelProgressResponse | |
level_up | *LevelUpResponse |
Example:
curl -X POST "http://localhost:8080/api/v1/checkins" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"checkpoint_id": "550e8400-e29b-41d4-a716-446655440000", "verification_method": "gps", "latitude": 37.5665, "longitude": 126.978, "accuracy_meters": 0.0, "qr_code_value": "value", "photo_url": "https://example.com/image.jpg", "note": "value"}'`POST /api/v1/checkins/photo-verify`
Description: verify a photo's EXIF GPS against a checkpoint. Form fields: - file: the image file (required, multipart, JPEG only for EXIF) - checkpoint_id: the UUID of the checkpoint to verify against (required) The handler extracts GPS coordinates from the photo's EXIF data and compares them with the checkpoint location. If the photo was taken within 200m of the checkpoint, the check-in is automatically created
Auth Required: Yes (Bearer token)
Request (multipart/form-data):
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | Image file (JPEG, PNG, HEIC, WebP) |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/checkins/photo-verify" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@photo.jpg"`GET /api/v1/checkins/me`
Description: Get My Checkins
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/checkins/me" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/checkins/:id`
Description: get single check-in detail
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/checkins/{id}" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/checkins/:id/verify`
Description: verify check-in status
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/checkins/{id}/verify" \
-H "Authorization: Bearer $TOKEN"Activities (활동)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/activities | Required | Create |
GET | /api/v1/activities/me | Required | Get My Activities |
GET | /api/v1/activities/summary | Required | Get Summary |
GET | /api/v1/activities/:id | Required | Get By ID |
GET | /api/v1/activities/:id/gpx | Required | download activity as GPX file |
POST | /api/v1/activities/:id/complete | Required | Complete |
POST | /api/v1/activities/:id/pause | Required | Pause |
POST | /api/v1/activities/:id/resume | Required | Resume |
POST | /api/v1/activities/:id/photos | Required | Form fields: - file: the image file (required, multipart) - latitude: GPS lat... |
DELETE | /api/v1/activities/:id | Required | Discard |
`POST /api/v1/activities`
Description: Create
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
trail_id | uuid? | No | |
activity_type | string | Yes | One of: hiking, walking, running, cycling, driving, trail_running |
title | string? | No | Max: 255 |
is_public | boolean | No |
Response (201):
{
"success": true,
"data": { /* ActivityResponse */ }
}Response data fields (ActivityResponse):
| Field | Type | Description |
|---|---|---|
id | uuid | |
user_id | uuid | |
trail_id | uuid? | |
activity_type | string | |
status | string | |
started_at | datetime | |
finished_at | datetime? | |
duration_seconds | integer? | |
distance_km | number? | |
avg_speed_kmh | number? | |
max_speed_kmh | number? | |
elevation_gain_m | integer? | |
elevation_loss_m | integer? | |
calories_burned | integer? | |
step_count | integer? | |
title | string? | |
notes | string? | |
is_public | boolean | |
created_at | datetime |
Example:
curl -X POST "http://localhost:8080/api/v1/activities" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"trail_id": "550e8400-e29b-41d4-a716-446655440000", "activity_type": "hiking", "title": "Sample Title", "is_public": true}'`GET /api/v1/activities/me`
Description: Get My Activities
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/activities/me" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/activities/summary`
Description: Get Summary
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/activities/summary" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/activities/:id`
Description: Get By ID
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/activities/{id}" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/activities/:id/gpx`
Description: download activity as GPX file
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/activities/{id}/gpx" \
-H "Authorization: Bearer $TOKEN"`POST /api/v1/activities/:id/complete`
Description: Complete
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
distance_km | number? | No | >= 0 |
elevation_gain_m | integer? | No | |
elevation_loss_m | integer? | No | |
calories_burned | integer? | No | >= 0 |
step_count | integer? | No | >= 0 |
notes | string? | No | Max: 1000 |
Response (200):
{
"success": true,
"data": { ... }
}Response data fields — 보상 정책 (ActivityResponse, 2026-09-10 신설 키 2 + 2026-09-11 신설 키 2):
| Field | Type | Description | |
|---|---|---|---|
xp_withheld_reason | string? | 보상이 판정으로 0 이 된 사유. 자유 활동(트레일 미결박)은 below_min_distance(서버 실측 거리가 하한 미만, 궤적 없는 완료도 같은 값). 트레일 완주를 주장한 활동(영수증 정산 경로)은 recompletion_cooldown\ | daily_cap. 보류 없으면 키 부재(= null 로 읽는다). |
reward_min_km | number? | 이 완료에 실제로 적용된 거리 하한(km). 하한이 걸리는 경로(자유 활동)에서만 실린다. | |
xp_ratio | number? | 2026-09-11 신설. 트레일 완주를 주장한 활동이 영수증 정산기를 지났을 때만 실린다. 첫 완주 1.0 · 쿨다운 지난 재완주 0.5 · 보류(쿨다운/일일상한) 0.0. xp_earned = 100 × xp_ratio. 자유 활동에는 없다(nil). | |
recompletion_eligible_at | string? (RFC3339) | 2026-09-11 신설. xp_withheld_reason == "recompletion_cooldown" 일 때만 실린다. "이 완주가 그때 지급된다"가 아니라 "그때 다시 걸으면 받는다"의 시각 — 보류는 종단이고 소급 지급이 없다. |
정책 요약 (결정서 §3):
- 자유 활동(트레일 미결박) 완료 보상의 서버 실측 거리 하한: 0.05 km → 1.0 km. 미달이면 활동 기록은 저장되고 XP 만 0 이다.
- 시간 조건은 없다. 서버가 도출한 활동 시간이 없고, 유일한 시간(
duration_seconds)은 클라이언트 저작이라 보상 지표에 실을 수 없다. - 이어걷기 예외: 종주 구간 완주로 성립한 활동에는 하한이 적용되지 않는다. 그 활동은 트레일을 주장하므로 완주 영수증 정산기가 보상을 내고, 그 경로에는 거리 하한이 없다 —
xp_withheld_reason/reward_min_km은 그 응답에 실리지 않는다(대신 쿨다운/일일상한이 적용되면xp_ratio/recompletion_eligible_at이 실린다). - 모바일은 `POST /trails/:id/complete` 를 호출하지 않는다(2026-09-11 실측) — 트레일 완주를 주장한 활동의 재완주 쿨다운·일일상한 사실은 이 엔드포인트(
ActivityResponse)로만 앱에 도달한다.TrailCompletionResponse(웹/관리 도구 전용)는 같은 사실을 같은 함수(rewardPolicyFieldsFromSettlement)로 낸다. - 네 키 모두 완주 응답에만 나타난다. 생성/목록 응답에는 없다.
Example:
curl -X POST "http://localhost:8080/api/v1/activities/{id}/complete" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"distance_km": 5.2, "elevation_gain_m": 1, "elevation_loss_m": 1, "calories_burned": 1, "step_count": 1, "notes": "value"}'`POST /api/v1/activities/:id/pause`
Description: Pause
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/activities/{id}/pause" \
-H "Authorization: Bearer $TOKEN"`POST /api/v1/activities/:id/resume`
Description: Resume
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/activities/{id}/resume" \
-H "Authorization: Bearer $TOKEN"`POST /api/v1/activities/:id/photos`
Description: Form fields: - file: the image file (required, multipart) - latitude: GPS latitude where the photo was taken (required) - longitude: GPS longitude where the photo was taken (required) - captured_at: ISO 8601 timestamp when the photo was taken (required) - caption: optional text caption for the photo Photos are stored in MinIO under the "activity" category with the activity ID prefix
Auth Required: Yes (Bearer token)
Request (multipart/form-data):
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | Image file (JPEG, PNG, HEIC, WebP) |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/activities/{id}/photos" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@photo.jpg"`DELETE /api/v1/activities/:id`
Description: Discard
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X DELETE "http://localhost:8080/api/v1/activities/{id}" \
-H "Authorization: Bearer $TOKEN"Achievements (업적)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/achievements | None | Get All |
GET | /api/v1/achievements/me | Required | Supports ?status=earned/in_progress query parameter. Default: returns only ea... |
GET | /api/v1/achievements/:id | None | get single achievement detail |
`GET /api/v1/achievements`
Description: Get All
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/achievements"`GET /api/v1/achievements/me`
Description: Supports ?status=earned|in_progress query parameter. Default: returns only earned achievements (earned_at IS NOT NULL)
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/achievements/me" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/achievements/:id`
Description: get single achievement detail
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/achievements/{id}"Missions (미션)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/missions | None | Get Missions |
GET | /api/v1/missions/me | Required | Get My Missions |
`GET /api/v1/missions`
Description: Get Missions
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/missions"`GET /api/v1/missions/me`
Description: Get My Missions
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/missions/me" \
-H "Authorization: Bearer $TOKEN"Gamification (게이미피케이션)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/gamification/level | Required | Get Level Progress |
GET | /api/v1/gamification/leaderboard | Required | Get Leaderboard |
`GET /api/v1/gamification/level`
Description: Get Level Progress
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/gamification/level" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/gamification/leaderboard`
Description: Get Leaderboard
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { /* GroupLeaderboardResponse */ }
}Response data fields (GroupLeaderboardResponse):
| Field | Type | Description |
|---|---|---|
board_type | string | |
period | string | |
group_range | string | |
group_size | integer | |
my_group_rank | integer? | |
entries | []LeaderboardEntryResponse |
Example:
curl -X GET "http://localhost:8080/api/v1/gamification/leaderboard" \
-H "Authorization: Bearer $TOKEN"Leaderboard (리더보드)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/leaderboard | Optional | Get Leaderboard |
GET | /api/v1/leaderboard/me | Required | Returns the authenticated user's rank across all leaderboard categories |
`GET /api/v1/leaderboard`
Description: Get Leaderboard
Auth Required: Optional (enhances response with user context)
Response (200):
{
"success": true,
"data": { /* GroupLeaderboardResponse */ }
}Response data fields (GroupLeaderboardResponse):
| Field | Type | Description |
|---|---|---|
board_type | string | |
period | string | |
group_range | string | |
group_size | integer | |
my_group_rank | integer? | |
entries | []LeaderboardEntryResponse |
Example:
curl -X GET "http://localhost:8080/api/v1/leaderboard"`GET /api/v1/leaderboard/me`
Description: Returns the authenticated user's rank across all leaderboard categories
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { /* MyRankingResponse */ }
}Response data fields (MyRankingResponse):
| Field | Type | Description |
|---|---|---|
user_id | uuid | |
username | string | |
display_name | string | |
level | integer | |
total_xp | integer | |
global_rank | integer? | |
global_score | number | |
global_total | integer? | |
weekly_rank | integer? | |
weekly_score | number | |
weekly_total | integer? |
Example:
curl -X GET "http://localhost:8080/api/v1/leaderboard/me" \
-H "Authorization: Bearer $TOKEN"Stamps (스탬프)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/stamps | Required | list user's stamp collection |
GET | /api/v1/stamps/collections | Required | list stamp collection sets grouped by trail |
GET | /api/v1/stamps/daily-bonus | Required | Returns the current daily bonus status including streak info, XP multiplier, ... |
GET | /api/v1/stamps/evolution | Required | Returns stamp evolution progress and earned evolved stamps. Tracks three evol... |
GET | /api/v1/stamps/encounters | Required | Returns all random encounter stamps found by the user. Includes hidden stamps... |
GET | /api/v1/stamps/gamification | Required | Returns a combined overview of all gamification systems: daily bonus, evoluti... |
`GET /api/v1/stamps`
Description: list user's stamp collection
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/stamps" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/stamps/collections`
Description: list stamp collection sets grouped by trail
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/stamps/collections" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/stamps/daily-bonus`
Description: Returns the current daily bonus status including streak info, XP multiplier, and progress toward weekly/monthly stamps
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { /* DailyBonusResult */ }
}Response data fields (DailyBonusResult):
| Field | Type | Description |
|---|---|---|
xp_earned | integer | |
xp_multiplier | number | |
bonus_xp | integer | |
is_first_of_day | boolean | |
consecutive_days | integer | |
weekly_stamp_earned | boolean | |
weekly_stamp_name | string? | |
monthly_stamp_earned | boolean | |
monthly_stamp_name | string? |
Example:
curl -X GET "http://localhost:8080/api/v1/stamps/daily-bonus" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/stamps/evolution`
Description: Returns stamp evolution progress and earned evolved stamps. Tracks three evolution tiers: - Silver: 3 common stamps in same region - Gold: all stamps in a trail - Platinum: 3 complete trail systems
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { /* StampEvolutionResponse */ }
}Response data fields (StampEvolutionResponse):
| Field | Type | Description |
|---|---|---|
progress | []EvolutionProgressItem | |
earned | []EarnedEvolutionItem |
Example:
curl -X GET "http://localhost:8080/api/v1/stamps/evolution" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/stamps/encounters`
Description: Returns all random encounter stamps found by the user. Includes hidden stamps (5%), seasonal stamps (cherry blossom, etc.), and weather-based stamps (rain, snow, sunrise)
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/stamps/encounters" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/stamps/gamification`
Description: Returns a combined overview of all gamification systems: daily bonus, evolution progress, and random encounters
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { /* StampGamificationOverview */ }
}Response data fields (StampGamificationOverview):
| Field | Type | Description |
|---|---|---|
daily_bonus | DailyBonusInfo | |
evolution | StampEvolutionResponse | |
random_encounters | []RandomEncounterResult | |
total_stamps | integer | |
total_evolutions | integer | |
total_encounters | integer |
Example:
curl -X GET "http://localhost:8080/api/v1/stamps/gamification" \
-H "Authorization: Bearer $TOKEN"Checkpoints (체크포인트)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/checkpoints/nearby | Required | Returns checkpoints within the specified radius with stamp rarity, collection... |
POST | /api/v1/checkpoints/:id/qr-generate | Required | Admin endpoint that generates a signed QR code for a checkpoint |
`GET /api/v1/checkpoints/nearby`
Description: Returns checkpoints within the specified radius with stamp rarity, collection status, and random encounter info. This is the "Stamp Nearby!" feature (Pokemon GO PokeStop style)
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/checkpoints/nearby" \
-H "Authorization: Bearer $TOKEN"`POST /api/v1/checkpoints/:id/qr-generate`
Description: Admin endpoint that generates a signed QR code for a checkpoint
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
expires_at | string? | No |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/checkpoints/{id}/qr-generate" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"expires_at": "value"}'Segments (구간)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/segments/:id/leaderboard | None | Returns the top records (최고 기록) for a segment, ranked by fastest time |
POST | /api/v1/segments/:id/records | Required | Submits a new timed record (구간 기록) for a segment |
`GET /api/v1/segments/:id/leaderboard`
Description: Returns the top records (최고 기록) for a segment, ranked by fastest time
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/segments/{id}/leaderboard"`POST /api/v1/segments/:id/records`
Description: Submits a new timed record (구간 기록) for a segment
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
duration_seconds | integer | Yes | Min: 1 |
avg_speed_kmh | number? | No | >= 0 |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/segments/{id}/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"duration_seconds": 60, "avg_speed_kmh": 5.2}'Feed (피드)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/feed | Optional | Get Feed |
GET | /api/v1/feed/following | Required | Get Following Feed |
GET | /api/v1/feed/activity | Optional | Get Activity Feed |
GET | /api/v1/feed/:id | Optional | alias for getting a single post |
`GET /api/v1/feed`
Description: Get Feed
Auth Required: Optional (enhances response with user context)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/feed"`GET /api/v1/feed/following`
Description: Get Following Feed
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/feed/following" \
-H "Authorization: Bearer $TOKEN"`GET /api/v1/feed/activity`
Description: Get Activity Feed
Auth Required: Optional (enhances response with user context)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/feed/activity"`GET /api/v1/feed/:id`
Description: alias for getting a single post
Auth Required: Optional (enhances response with user context)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/feed/{id}"Posts (게시물)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/posts | Required | Create Post |
POST | /api/v1/posts/:id/like | Required | Like Post |
DELETE | /api/v1/posts/:id/like | Required | Unlike Post |
POST | /api/v1/posts/:id/comments | Required | Create Comment |
GET | /api/v1/posts/:id/comments | None | Get Comments |
`POST /api/v1/posts`
Description: Create Post
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
content_text | string? | No | Max: 2000 |
activity_id | uuid? | No | |
trail_id | uuid? | No | |
photos | string[] | No | Max: 10 |
visibility | string | No | One of: public, followers, private |
latitude | number? | No | Valid latitude |
longitude | number? | No | Valid longitude |
location_name | string? | No | Max: 255 |
Response (201):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/posts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"content_text": "Sample text content", "activity_id": "550e8400-e29b-41d4-a716-446655440000", "trail_id": "550e8400-e29b-41d4-a716-446655440000", "photos": ["item1", "item2"], "visibility": "public", "latitude": 37.5665, "longitude": 126.978, "location_name": "Sample Name"}'`POST /api/v1/posts/:id/like`
Description: Like Post
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/posts/{id}/like" \
-H "Authorization: Bearer $TOKEN"`DELETE /api/v1/posts/:id/like`
Description: Unlike Post
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X DELETE "http://localhost:8080/api/v1/posts/{id}/like" \
-H "Authorization: Bearer $TOKEN"`POST /api/v1/posts/:id/comments`
Description: Create Comment
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
content | string | Yes | Min: 1; Max: 1000 |
parent_comment_id | uuid? | No |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/posts/{id}/comments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "Sample text content", "parent_comment_id": "550e8400-e29b-41d4-a716-446655440000"}'`GET /api/v1/posts/:id/comments`
Description: Get Comments
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/posts/{id}/comments"Kudos (응원)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/kudos | Required | Send Kudos |
`POST /api/v1/kudos`
Description: Send Kudos
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
checkin_id | uuid | Yes | |
emoji | string | Yes | One of: thumbs_up, fire, heart, clap, mountain, star |
message | string? | No | Max: 200 |
Response (201):
{
"success": true,
"data": { /* KudosResponse */ }
}Response data fields (KudosResponse):
| Field | Type | Description |
|---|---|---|
id | uuid | |
checkin_id | uuid | |
emoji | string | |
message | string? | |
created_at | datetime |
Example:
curl -X POST "http://localhost:8080/api/v1/kudos" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"checkin_id": "550e8400-e29b-41d4-a716-446655440000", "emoji": "thumbs_up", "message": "value"}'Events (이벤트)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/events | Required | Create Group Event |
POST | /api/v1/events/:id/rsvp | Required | Update RSVP |
`POST /api/v1/events`
Description: Create Group Event
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
trail_id | uuid | Yes | |
title | string | Yes | Min: 3; Max: 255 |
description | string? | No | Max: 2000 |
event_date | string | Yes | |
max_members | integer | Yes | Min: 2; Max: 100 |
is_public | boolean | No |
Response (201):
{
"success": true,
"data": { /* GroupEventResponse */ }
}Response data fields (GroupEventResponse):
| Field | Type | Description |
|---|---|---|
id | uuid | |
organizer_id | uuid | |
trail_id | uuid | |
trail_name | string | |
title | string | |
description | string? | |
event_date | datetime | |
max_members | integer | |
is_public | boolean | |
status | string | |
going_count | integer | |
maybe_count | integer | |
user_rsvp | string? | |
created_at | datetime |
Example:
curl -X POST "http://localhost:8080/api/v1/events" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"trail_id": "550e8400-e29b-41d4-a716-446655440000", "title": "Sample Title", "description": "Sample text content", "event_date": "2026-03-30", "max_members": 20, "is_public": true}'`POST /api/v1/events/:id/rsvp`
Description: Update RSVP
Auth Required: Yes (Bearer token)
Request Body (application/json):
| Field | Type | Required | Validation |
|---|---|---|---|
status | string | Yes | One of: going, maybe, not_going |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/events/{id}/rsvp" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "going"}'Notifications (알림)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/notifications | Required | returns the current user's notifications |
PATCH | /api/v1/notifications/:id/read | Required | marks a notification as read |
`GET /api/v1/notifications`
Description: returns the current user's notifications
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/notifications" \
-H "Authorization: Bearer $TOKEN"`PATCH /api/v1/notifications/:id/read`
Description: marks a notification as read
Auth Required: Yes (Bearer token)
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X PATCH "http://localhost:8080/api/v1/notifications/{id}/read" \
-H "Authorization: Bearer $TOKEN"Upload (업로드)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/upload/photo | Required | upload a photo for check-in verification, social posts, or activities. Form ... |
POST | /api/v1/upload/avatar | Required | upload a profile avatar image. Form fields: - file: the image file (required... |
`POST /api/v1/upload/photo`
Description: upload a photo for check-in verification, social posts, or activities. Form fields: - file: the image file (required, multipart) - category: one of "checkin", "social", "activity" (optional, defaults to "checkin") Returns the photo URL, thumbnail URL, filename, size, and MIME type
Auth Required: Yes (Bearer token)
Request (multipart/form-data):
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | Image file (JPEG, PNG, HEIC, WebP) |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/upload/photo" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@photo.jpg"`POST /api/v1/upload/avatar`
Description: upload a profile avatar image. Form fields: - file: the image file (required, multipart, max 5MB) Avatars are stored in the "avatars" bucket which has public read access. Previous avatars are NOT deleted automatically (handled by a separate cleanup job)
Auth Required: Yes (Bearer token)
Request (multipart/form-data):
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | Image file (JPEG, PNG, HEIC, WebP) |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/upload/avatar" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@photo.jpg"Nearby (주변)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/nearby/amenities | None | Query parameters: - lat: latitude (required) - lng: longitude (required) - ra... |
`GET /api/v1/nearby/amenities`
Description: Query parameters: - lat: latitude (required) - lng: longitude (required) - radius: search radius in meters (optional, default 1000, max 5000) - type: amenity type filter (optional, e.g. "convenience_store", "restroom") - limit: maximum results (optional, default 20, max 50)
Auth Required: No
Query Parameters:
| Field | Type | Required | Validation |
|---|---|---|---|
lat | number | Yes | Valid latitude |
lng | number | Yes | Valid longitude |
radius | number | No | > 0; Max: 5000 |
type | string | No | One of: convenience_store, restroom, water, rest_area, restaurant, cafe, parking, bus_stop |
limit | integer | No | Min: 1; Max: 50 |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/nearby/amenities"Weather (날씨)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/weather | None | Returns real weather data if an API key is configured, otherwise returns real... |
`GET /api/v1/weather`
Description: Returns real weather data if an API key is configured, otherwise returns realistic mock data generated from the latitude, time of day, and season
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/weather"Recommendations (추천)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/recommendations/quick-start | None | Query parameters: lat, lng, minutes (default 30), activity (default "walk") T... |
`GET /api/v1/recommendations/quick-start`
Description: Query parameters: lat, lng, minutes (default 30), activity (default "walk") Tries the real recommendation service first. If no routes are found (e.g., DB is empty or no trails nearby), returns realistic mock data so the Flutter app always has content
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/recommendations/quick-start"GPX
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/gpx/upload | Required | Upload |
POST | /api/v1/gpx/parse | None | (parse without saving) |
`POST /api/v1/gpx/upload`
Description: Upload
Auth Required: Yes (Bearer token)
Request (multipart/form-data):
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | Image file (JPEG, PNG, HEIC, WebP) |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/gpx/upload" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@photo.jpg"`POST /api/v1/gpx/parse`
Description: (parse without saving)
Auth Required: No
Request (multipart/form-data):
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | Image file (JPEG, PNG, HEIC, WebP) |
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X POST "http://localhost:8080/api/v1/gpx/parse" \
-F "file=@photo.jpg"Knowledge Graph (지식 그래프)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/v1/graph/node/:id | None | Returns a node with all outgoing connections and backlinks (Obsidian-style) |
GET | /api/v1/graph/explore | None | Performs multi-hop graph traversal from a center node |
GET | /api/v1/graph/search | None | Full-text search across all knowledge nodes |
GET | /api/v1/graph/nearby | None | Finds knowledge nodes near a geographic point using PostGIS |
GET | /api/v1/graph/stats | None | Returns summary statistics for the knowledge graph |
`GET /api/v1/graph/node/:id`
Description: Returns a node with all outgoing connections and backlinks (Obsidian-style)
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/graph/node/{id}"`GET /api/v1/graph/explore`
Description: Performs multi-hop graph traversal from a center node
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/graph/explore"`GET /api/v1/graph/search`
Description: Full-text search across all knowledge nodes
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/graph/search"`GET /api/v1/graph/nearby`
Description: Finds knowledge nodes near a geographic point using PostGIS
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/graph/nearby"`GET /api/v1/graph/stats`
Description: Returns summary statistics for the knowledge graph
Auth Required: No
Response (200):
{
"success": true,
"data": { ... }
}Example:
curl -X GET "http://localhost:8080/api/v1/graph/stats"Statistics
| Metric | Count |
|---|---|
| Total Endpoints | 96 |
| GET Endpoints | 63 |
| POST Endpoints | 28 |
| PATCH Endpoints | 2 |
| DELETE Endpoints | 3 |
| Auth Required | 53 |
| Auth Optional | 4 |
| Public (No Auth) | 39 |
| Endpoint Groups | 24 |
| Handler Documentation Coverage | 100% (94/94) | | DTOs Referenced | 39 | | Total DTOs Parsed | 98 |
*Generated by scripts/generate_api_docs.py on 2026-03-30 07:17 KST*