Errors
HTTP status codes, error envelope, and error codes returned by the API.
Error envelope
Most errors return a JSON error object with a machine‑readable code, a human‑readable
message, and an optional details array, alongside the request meta:
{
"error": {
"code": "agent_unavailable",
"message": "Agent is currently disabled.",
"details": []
},
"meta": {
"request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f"
}
}A few failures bypass the envelope and return Laravel's bare { "message": "..." } shape. Branch
on the HTTP status code first, then read error.code when the body carries an error object:
401Unauthenticated — a missing, malformed, revoked, or expired bearer key:{ "message": "Unauthenticated." }.403/404from ownership or route binding — an agent (or other resource) you don't own, or a UUID that doesn't resolve:{ "message": "This action is unauthorized." }.
Everything else uses the envelope, including validation (422 validation_failed), a missing
scope, an expired key, and a missing premium plan.
Validation errors
When a request body or query parameter fails validation, the API returns 422 with the standard
envelope, code: "validation_failed", and a details array naming each invalid field:
{
"error": {
"code": "validation_failed",
"message": "The content field is required.",
"details": [
{ "field": "content", "message": "The content field is required." }
]
},
"meta": { "request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f" }
}message repeats the first field error for convenience; iterate details — each entry is
{ field, message } — to surface every problem at once.
This is not Laravel's default { "message", "errors": { "field": [...] } } validation shape.
The API normalizes validation into the same error.code / error.details envelope as every
other error, so read error.details[].field and error.details[].message.
Status codes
| Status | Meaning |
|---|---|
200 OK | Request succeeded. |
201 Created | Resource created. |
202 Accepted | Asynchronous job accepted (crawl started). |
204 No Content | Succeeded with no body (delete). |
401 Unauthorized | Missing/invalid key, or the key has expired. |
403 Forbidden | Not the owner, missing scope, or no premium plan. |
404 Not Found | The agent, source, or training job does not exist, or the source/job does not belong to the agent in the path. A resource that exists but you don't own returns 403, not 404. |
422 Unprocessable Entity | Validation failed or a subscription limit was reached. |
423 Locked | The agent is disabled and cannot accept the operation. |
429 Too Many Requests | Rate limit exceeded — see Rate Limits. |
502 Bad Gateway | An upstream dependency (storage or the training service) failed. |
Error codes
These appear as error.code in the envelope:
code | Status | Meaning |
|---|---|---|
token_expired | 401 | The API key passed its expiry date. |
premium_required | 403 | Organization lacks an active premium/AppSumo plan. |
insufficient_scope | 403 | The key is missing the scope this endpoint requires. |
forbidden | 403 | The key's user has no organization, or is not the organization owner (returned by source creation when the owner-only check fails). |
resource_not_found | 404 | The source or training job was not found for this agent. |
validation_failed | 422 | A request body or query parameter failed validation; details[] lists each { field, message }. Also covers domain-rule failures such as an invalid source type on retrain. |
subscription_limit_reached | 422 | An agent‑count limit on the plan was reached. |
subscription_limit_exceeded | 422 | A training quota (text, URLs, or document size) was exceeded. |
premium_plan_required | 422 | A premium‑only model or setting was requested on a non‑premium plan. |
agent_unavailable | 423 | The agent is disabled, so source/model writes are blocked. |
upstream_failure | 502 | Storage upload, crawl, retrain, or status lookup failed upstream. |
Examples
403 insufficient_scope
{
"error": {
"code": "insufficient_scope",
"message": "Token is missing required scope: source:create",
"details": []
},
"meta": { "request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f" }
}422 validation_failed
{
"error": {
"code": "validation_failed",
"message": "The content field is required.",
"details": [
{ "field": "content", "message": "The content field is required." }
]
},
"meta": { "request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f" }
}423 agent_unavailable
{
"error": {
"code": "agent_unavailable",
"message": "Agent is currently disabled.",
"details": []
},
"meta": { "request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f" }
}Include the X-Request-ID response header (also in meta.request_id) when contacting support —
it lets us trace the exact request in our logs.