Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions api-reference/errors-and-status-codes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,47 @@
description: "Common status code semantics and error-handling expectations for non-admin API flows."
---

## Error envelope

All error responses use a consistent JSON envelope:

```json
{
"success": false,
"error": "Human-readable message",
"code": "MACHINE_READABLE_CODE"
}
```

The `code` field is a stable, machine-readable identifier you can match on without parsing the message string.

## Common status code patterns

- `200` / `201`: successful read or mutation.
- `400`: invalid payload or missing required fields.
- `401`: authentication missing or invalid.
- `404`: resource not found.
- `409`: state conflict (when route-specific logic enforces uniqueness/state).
- `500`: unexpected server-side failure.
- `400`: invalid payload or missing required fields (`VALIDATION_ERROR`).
- `401`: authentication missing or invalid (`UNAUTHORIZED`).
- `404`: resource not found (`NOT_FOUND`).
- `409`: state conflict such as duplicate action or prerequisite not met (`CONFLICT`).
- `500`: unexpected server-side failure (`INTERNAL_ERROR`).

## Domain-specific error codes

Quest and reward routes propagate typed errors from the server action layer. When the action throws a domain error, the route preserves the original HTTP status and error code instead of collapsing to a generic `500`. Common codes you may encounter:

| Code | Status | Description |
|------|--------|-------------|
| `VALIDATION_ERROR` | 400 | Missing or invalid request fields |
| `UNAUTHORIZED` | 401 | No valid session token |
| `NOT_FOUND` | 404 | Quest, step, or reward does not exist |
| `CONFLICT` | 409 | Reward already redeemed or must be claimed first |
| `QUEST_DAILY_VISIT_NOT_ELIGIBLE` | 400 | Daily visit already recorded or not eligible |
| `INTERNAL_ERROR` | 500 | Unexpected failure |

## Error handling guidance

- Match on the `code` field for programmatic error handling rather than parsing the `error` message.
- Treat all writes as potentially retriable only when idempotency is guaranteed by route semantics.

Check warning on line 45 in api-reference/errors-and-status-codes.mdx

View check run for this annotation

Mintlify / Mintlify Validation (chronoscyberchronicles) - vale-spellcheck

api-reference/errors-and-status-codes.mdx#L45

Did you really mean 'retriable'?

Check warning on line 45 in api-reference/errors-and-status-codes.mdx

View check run for this annotation

Mintlify / Mintlify Validation (chronoscyberchronicles) - vale-spellcheck

api-reference/errors-and-status-codes.mdx#L45

Did you really mean 'idempotency'?
- Surface response body messages in client logs for operational debugging.
- Prefer route-specific error handling over global assumptions.

## Where to verify

Expand Down
56 changes: 48 additions & 8 deletions api-reference/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,11 @@
"description": "Query: category (optional) for QuestCategory; else active quests.",
"parameters": [{ "name": "category", "in": "query", "schema": { "type": "string" } }],
"security": [{ "bearerAuth": [] }],
"responses": { "200": { "description": "{ quests, currentUserId }" }, "401": { "description": "Authentication required" } }
"responses": {
"200": { "description": "{ quests, currentUserId }" },
"401": { "description": "Authentication required (UNAUTHORIZED)" },
"500": { "description": "Unexpected server error (INTERNAL_ERROR)" }
}
}
},
"/api/quests/start": {
Expand All @@ -697,7 +701,12 @@
}
},
"security": [{ "bearerAuth": [] }],
"responses": { "200": { "description": "{ quest, success }" } }
"responses": {
"200": { "description": "{ quest, success }" },
"400": { "description": "Quest ID is required (VALIDATION_ERROR). Domain errors from the action layer preserve their original status." },
"401": { "description": "Authentication required (UNAUTHORIZED)" },
"500": { "description": "Unexpected server error (INTERNAL_ERROR)" }
}
}
},
"/api/quests/complete-step": {
Expand All @@ -718,15 +727,25 @@
}
},
"security": [{ "bearerAuth": [] }],
"responses": { "200": { "description": "{ success, pointsAwarded, questCompleted }" } }
"responses": {
"200": { "description": "{ success, pointsAwarded, questCompleted }" },
"400": { "description": "Quest ID and Step ID are required (VALIDATION_ERROR)" },
"401": { "description": "Authentication required (UNAUTHORIZED)" },
"500": { "description": "Unexpected server error (INTERNAL_ERROR)" }
}
}
},
"/api/quests/daily-visit": {
"post": {
"tags": ["Quests"],
"summary": "Daily visit",
"security": [{ "bearerAuth": [] }],
"responses": { "200": { "description": "Daily visit result" } }
"responses": {
"200": { "description": "Daily visit result" },
"400": { "description": "Not eligible for daily visit (QUEST_DAILY_VISIT_NOT_ELIGIBLE)" },
"401": { "description": "Authentication required (UNAUTHORIZED)" },
"500": { "description": "Unexpected server error (INTERNAL_ERROR)" }
}
}
},
"/api/quests/check-progress": {
Expand All @@ -741,15 +760,24 @@
}
},
"security": [{ "bearerAuth": [] }],
"responses": { "200": { "description": "{ success }" } }
"responses": {
"200": { "description": "{ success, status, actionStatus }" },
"400": { "description": "A valid questType is required (VALIDATION_ERROR)" },
"401": { "description": "Authentication required (UNAUTHORIZED)" },
"500": { "description": "Unexpected server error (INTERNAL_ERROR)" }
}
}
},
"/api/rewards": {
"get": {
"tags": ["Rewards", "Gamification"],
"summary": "List rewards",
"security": [{ "bearerAuth": [] }],
"responses": { "200": { "description": "{ rewards, currentUserId }" } }
"responses": {
"200": { "description": "{ rewards, currentUserId }" },
"401": { "description": "Authentication required (UNAUTHORIZED)" },
"500": { "description": "Unexpected server error (INTERNAL_ERROR)" }
}
}
},
"/api/rewards/claim": {
Expand All @@ -764,7 +792,12 @@
}
},
"security": [{ "bearerAuth": [] }],
"responses": { "200": { "description": "{ reward, success }" } }
"responses": {
"200": { "description": "{ reward, success }" },
"400": { "description": "Reward ID is required (VALIDATION_ERROR). Domain errors from the action layer preserve their original status." },
"401": { "description": "Authentication required (UNAUTHORIZED)" },
"500": { "description": "Unexpected server error (INTERNAL_ERROR)" }
}
}
},
"/api/rewards/redeem": {
Expand All @@ -779,7 +812,14 @@
}
},
"security": [{ "bearerAuth": [] }],
"responses": { "200": { "description": "{ reward, success }" } }
"responses": {
"200": { "description": "{ reward, success }" },
"400": { "description": "Reward ID is required (VALIDATION_ERROR). Domain errors from the action layer preserve their original status." },
"401": { "description": "Authentication required (UNAUTHORIZED)" },
"404": { "description": "Reward not found (NOT_FOUND)" },
"409": { "description": "Reward already redeemed or must be claimed first (CONFLICT)" },
"500": { "description": "Unexpected server error (INTERNAL_ERROR)" }
}
}
},
"/api/wallets": {
Expand Down