The Madless API uses conventional HTTP status codes and returns all errors in the RFC 9457 Problem Details format. Every error response includes a machine-readable type, a human-readable title, the HTTP status code, and a detailed description of what went wrong.
Every error response uses application/problem+json as the content type. The instance field echoes the request path for easier debugging.
{
"type": "https://docs.madless.com/errors#not_found",
"title": "not found",
"status": 404,
"detail": "Concept cpt_abc123 not found.",
"instance": "/api/v1/canvas/cpt_abc123"
}Validation errors (400) include an additional errors array with one entry per failed field (detail, pointer, code) from Zod schema validation.
The type field is a stable URL that names the error class and deep-links here, e.g. https://docs.madless.com/errors#validation_error. Branch on it — never on detail, which is human-readable prose and may change.
| Type | Status | When |
|---|---|---|
| validation_error | 400 | Request body or query parameters failed schema validation. Carries an errors[] array with one entry per failed field (detail, pointer, code). |
| unauthorized | 401 | Missing, malformed, or invalid API key or session. |
| password_required | 401 | The resource is password-protected and no password was supplied. Resend with the share password. |
| ai_credits_exhausted | 402 | Your space has used its monthly AI credit allowance. Credits reset at the start of each billing cycle. |
| ai_usd_budget_exhausted | 402 | Your space's monthly USD AI budget is spent. Raise the budget in admin settings or wait for the next cycle. |
| ai_platform_budget_exhausted | 402 | The platform's shared daily AI budget is exhausted. Transient — try again later. |
| ai_daily_usd_cap_exhausted | 402 | Your space's daily USD AI cap has been reached. Resets at 00:00 UTC. |
| forbidden | 403 | Authenticated, but the key lacks the required scope or entitlement. |
| password_incorrect | 403 | A password was supplied for a protected resource but it did not match. |
| not_found | 404 | The addressed resource does not exist or is not visible to you. |
| method_not_allowed | 405 | The HTTP method is not supported for this path. The Allow header lists the methods that are. |
| conflict | 409 | The request conflicts with current state — a duplicate, or a reused Idempotency-Key with a different body. |
| precondition_failed | 412 | An If-Match / ETag precondition did not hold — the resource changed since you last read it. Refetch and retry. |
| too_large | 413 | The request payload exceeds the allowed size for this endpoint. |
| unsupported_media_type | 415 | The Content-Type (or the uploaded file MIME) is not accepted by this endpoint. |
| unprocessable_entity | 422 | The body was syntactically valid JSON but semantically unprocessable (e.g. malformed JSON, or a value that cannot be acted on). |
| rate_limited | 429 | Too many requests in the window. Respect the Retry-After header. |
| server_error | 500 | An unexpected internal failure. Safe to retry with backoff. |
| not_implemented | 501 | The endpoint exists but the requested capability is not implemented. |
| service_unavailable | 503 | A dependency is temporarily unavailable. Safe to retry with backoff. |
| ai_budget_check_failed | 503 | The AI budget service was unavailable, so the request was refused rather than billed. Safe to retry. |
| Code | Meaning | When |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource successfully created |
| 204 | No Content | Successful delete, no body returned |
| 400 | Bad Request | Invalid body or query parameters |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | Insufficient scopes or entitlements |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Duplicate or conflicting state |
| 422 | Unprocessable Entity | Schema validation failure |
| 429 | Rate Limited | Too many requests in window |
| 500 | Server Error | Unexpected internal failure |
When you exceed the rate limit for an endpoint, the API returns 429 Too Many Requests. Every response includes headers to help you manage your request budget:
| Header | Description |
|---|---|
| Retry-After | Seconds to wait before retrying the request. |
| X-RateLimit-Limit | Maximum number of requests allowed per window. |
| X-RateLimit-Remaining | Requests remaining in the current window. |
| X-RateLimit-Reset | Unix timestamp when the current window resets. |
All write endpoints (POST, PUT, PATCH) support idempotent requests. Include an Idempotency-Key header with a unique identifier (a UUID works well). If the server has already processed a request with the same key, it returns the original response without executing the operation again.
curl -X POST 'https://acme.madless.com/api/v1/canvas' \
-H 'Authorization: Bearer mad_sk_abc123...' \
-H 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
-H 'Content-Type: application/json' \
-d '{
"title": "Compound Intelligence",
"notes": "A knowledge graph that compounds over time."
}'Keys expire after 24 hours. Using the same key with a different request body returns 409 Conflict.
For 429 and 5xx errors, use exponential backoff with jitter. Always respect the Retry-After header when present. Do not retry 4xxerrors other than 429 — they indicate a problem with the request itself that must be fixed before retrying.