/api/public/v1 follow predictable shapes. Understanding them lets your integration distinguish transient failures (worth retrying) from permanent failures (worth alerting on).
HTTP status code semantics
| Status | Meaning | Integration action |
|---|---|---|
400 Bad Request | Malformed request body or invalid field values | Fix the request — do not retry |
401 Unauthorized | Missing, expired, or invalid API key | Check key configuration — do not retry until fixed |
403 Forbidden | Valid key but missing scope, or operation not permitted | Check scope list — do not retry until fixed |
404 Not Found | Resource does not exist | Do not retry; the resource is genuinely absent |
409 Conflict | Duplicate submission (same request_id already exists) | Treat as success — idempotent; read the existing record |
422 Unprocessable Entity | Request body passes JSON parsing but fails validation | Fix the request — do not retry |
429 Too Many Requests | Rate limit exceeded | Retry after the Retry-After header delay |
500 Internal Server Error | Unexpected server error | Retry with exponential backoff |
502 Bad Gateway | Upstream dependency failure | Retry with exponential backoff |
503 Service Unavailable | Server overloaded or in maintenance | Retry after Retry-After if present |
504 Gateway Timeout | Upstream timeout | Retry with exponential backoff |
Error envelope shapes
Authentication and authorization errors
FastAPI returns a plain JSON object for auth failures:detail strings you may see:
"Invalid authentication credentials"— bad or expired key"API key lacks required scope"— key exists but scope is wrong"Admin privileges required"— endpoint requires admin role
Validation errors (HTTP 422)
When request body validation fails, the server returns FastAPI’s standard validation error shape:loc is a list that traces the path to the failing field. type is a Pydantic error code. msg is a human-readable description.
Rate limit errors (HTTP 429)
Retry-After response header for the number of seconds to wait before retrying.
Server errors (HTTP 5xx)
POST /api/public/v1/requests — submitting the same request_id twice returns the existing record rather than a duplicate.
Retry classification
The Discourse plugin uses this classification (mirrors the server’s own webhook retry logic):0.5 s × 2^(attempt - 1) with a cap at 60 s and a maximum of 3 attempts. This matches the Discourse plugin’s INITIAL_BACKOFF = 0.5 / MAX_RETRIES = 3 constants.
Example: handling errors in an integration
- Ruby (Discourse plugin pattern)
- Python
Idempotency note
POST /api/public/v1/requests is idempotent on request_id. If you retry a request that already succeeded, the server returns HTTP 200 with the existing record rather than HTTP 201. Your integration should treat both 200 and 201 as success.
See also
- Headers and auth — auth failure modes
- Webhooks — delivery failure semantics
- API Reference — Requests — full endpoint spec