Errors
Errors are produced by a global HttpExceptionFilter. They are not wrapped in the success envelope — check the HTTP status first.
Status codes
| Status | Meaning | Common cause |
|---|---|---|
400 | Bad Request | Validation failure (missing/invalid body fields) |
401 | Unauthorized | Missing/invalid token, or insufficient role |
403 | Forbidden | Authenticated but not allowed for this resource |
404 | Not Found | Unknown id, or a resource you don’t own |
429 | Too Many Requests | A throttle limit was hit |
500 | Internal Server Error | Unexpected server failure |
Validation errors (400)
The global ValidationPipe rejects malformed bodies and returns the failing constraints:
{
"statusCode": 400,
"message": ["title must be a string", "content should not be empty"],
"error": "Bad Request"
}Unknown fields don’t cause a 400 — they’re silently stripped (whitelist: true). Only type/constraint violations on declared fields error.
Auth errors (401)
{ "statusCode": 401, "message": "User not authenticated" }Checklist when you see 401:
- Is the
Authorization: Bearer …header present and well-formed? - For a PAT: is it spelled
mr_pat_…, not revoked, and not expired? - For a Clerk JWT: are the publishable (frontend) and secret (backend) keys the same kind — both
testor bothlive? - For a role failure you’ll see
Insufficient permissions. Required: …— your token’s user lacks the required role (e.g. anadmin-only route).
Rate limits (429)
When a @Throttle (or the global throttler) limit is exceeded you get 429. Respect the limit windows listed in Platform API → Rate limits and back off before retrying.
Errors inside SSE streams
A streaming endpoint that fails mid-flight does not change the HTTP status (headers were already sent with 200). Instead it emits an error frame and ends the stream:
data: {"type":"error","runId":"clx...","error":"Upstream model timeout"}So for agent-service streams, handle the error event type, not just the HTTP status. See SSE events.
Handling pattern
async function call<T>(url: string, init?: RequestInit): Promise<T> {
const res = await fetch(url, init)
if (res.status === 429) throw new Error('Rate limited — back off and retry')
const body = await res.json()
if (!res.ok || body.code >= 400) {
// validation errors carry an array message
const msg = Array.isArray(body.message) ? body.message.join('; ') : body.message
throw new Error(msg || `HTTP ${res.status}`)
}
return body.data
}In development, agent-service logs an auth diagnostic (token kind, Clerk secret kind, issuer/audience claims) when a request fails authentication — check the server logs to pinpoint a misconfigured token or key pair.