## Changes - Add narrative API documentation in docs/api-content/ (introduction, quickstart, authentication, conventions, errors, rate limiting) - Scaffold Astro Starlight site at docs/api/ that consumes prose from the sibling api-content folder and auto-generates the endpoint reference from server/openapi.json via starlight-openapi - Wire Checkmate brand color (#13715B) into Starlight via custom CSS - Add vercel.json so the docs site deploys cleanly as an Astro project with root directory docs/api/ ## Benefits - Public API docs ready to publish at checkmate.so/docs - Endpoint reference stays in sync with the OpenAPI spec automatically - Zero third-party SaaS dependency: site is fully self-hosted on Vercel - Contributors edit plain markdown; no Astro knowledge needed for prose
125 lines
3.5 KiB
Markdown
125 lines
3.5 KiB
Markdown
---
|
|
title: Errors
|
|
description: HTTP status codes Checkmate returns, what they mean, and how to handle them.
|
|
---
|
|
|
|
# Errors
|
|
|
|
Failure responses share the same envelope as successful ones, with `success: false` and `data` omitted (or `null`):
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"msg": "Validation failed: email must be a valid email"
|
|
}
|
|
```
|
|
|
|
Always treat the **HTTP status code** as the source of truth for handling logic, and surface `msg` to humans.
|
|
|
|
## Status codes
|
|
|
|
| Status | Meaning | When you'll see it |
|
|
|---|---|---|
|
|
| `200 OK` | Request succeeded with a body | Most GETs, successful mutations that return the new resource |
|
|
| `204 No Content` | Request succeeded with no body | DELETEs, successful actions like `/status-page/:url/unlock` |
|
|
| `400 Bad Request` | Malformed request | Missing required path param, malformed JSON, invalid file upload |
|
|
| `401 Unauthorized` | Missing or invalid JWT | Token expired, token missing, token signed with a different secret |
|
|
| `403 Forbidden` | Authenticated, but not allowed | Accessing a resource that belongs to a different team, or insufficient role |
|
|
| `404 Not Found` | Resource doesn't exist | Wrong ID, deleted resource, mistyped path |
|
|
| `409 Conflict` | Operation would violate a constraint | Duplicate email on register, slug already taken on status page |
|
|
| `413 Payload Too Large` | File upload exceeded limit | Logo or profile image too big |
|
|
| `422 Unprocessable Entity` | Request was well-formed but failed validation | Zod / Joi schema rejected the body |
|
|
| `429 Too Many Requests` | Rate limit exceeded | See [Rate limiting](/api/rate-limiting) |
|
|
| `500 Internal Server Error` | Unexpected server error | A bug — report it with the request ID |
|
|
|
|
## Common shapes
|
|
|
|
### 401 — missing or invalid token
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"msg": "No auth token provided"
|
|
}
|
|
```
|
|
|
|
Re-authenticate by calling `POST /auth/login`.
|
|
|
|
### 422 — validation error
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"msg": "\"email\" must be a valid email"
|
|
}
|
|
```
|
|
|
|
The `msg` describes the first failing field. Re-send the request with the fix.
|
|
|
|
### 403 — forbidden
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"msg": "Forbidden"
|
|
}
|
|
```
|
|
|
|
You're authenticated but the resource isn't visible to you. Common cause: passing a `teamId` that isn't your team's.
|
|
|
|
### 429 — rate limited
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"msg": "Too many requests"
|
|
}
|
|
```
|
|
|
|
Inspect `RateLimit-*` response headers and back off. See [Rate limiting](/api/rate-limiting).
|
|
|
|
## Error handling pattern
|
|
|
|
Client code should branch on status code, not on `msg`:
|
|
|
|
```javascript
|
|
const res = await fetch(url, opts);
|
|
const body = await res.json().catch(() => ({}));
|
|
|
|
switch (res.status) {
|
|
case 200:
|
|
case 204:
|
|
return body.data;
|
|
|
|
case 401:
|
|
// Token expired or missing — re-authenticate, then retry once.
|
|
await reauthenticate();
|
|
return retry();
|
|
|
|
case 422:
|
|
// Surface the validation error to the user.
|
|
throw new ValidationError(body.msg);
|
|
|
|
case 429:
|
|
// Back off using Retry-After if present.
|
|
const retryAfter = Number(res.headers.get("Retry-After") ?? 60);
|
|
await sleep(retryAfter * 1000);
|
|
return retry();
|
|
|
|
default:
|
|
throw new Error(`Checkmate API ${res.status}: ${body.msg ?? res.statusText}`);
|
|
}
|
|
```
|
|
|
|
## Reporting bugs
|
|
|
|
If you see a `500`, capture:
|
|
|
|
- The exact request (method, path, body — **redact secrets**)
|
|
- The full response body and headers
|
|
- A timestamp in UTC
|
|
|
|
Open an issue at https://github.com/bluewave-labs/Checkmate/issues with that information.
|
|
|
|
Next: [Rate limiting](/api/rate-limiting).
|