Error envelope
Every v2 error response shares the same shape — an error object with a stable code, a human-readable message, optional structured details, and a request_id for support escalation.
bash
{
"error": {
"code": "validation_error",
"message": "Field 'question' must be a non-empty string.",
"details": {
"field": "question",
"received": ""
},
"request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
}- code — Stable, machine-readable string. Switch on this in your error handler — never the message.
- message — Human-readable description. Safe to log; not safe to surface to end-users without translation.
- details — Optional structured payload. Shape varies by code — for validation_error it's the offending field and value.
- request_id — Unique per request. Quote this in any support ticket — we can find the trace from this alone.
Code registry
Every code we emit, what it means, and the HTTP status it travels on.
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request was malformed (bad JSON, missing required field, wrong content-type). |
| 401 | unauthenticated | Your API key is missing, malformed, or revoked. |
| 402 | insufficient_credits | Your tenant ran out of credits. Top up before retrying. |
| 403 | forbidden | Your key is valid but lacks permission for this resource (often a tenant mismatch). |
| 404 | job_not_found | No job exists with that ID — or it belongs to another tenant and you can't see it. |
| 404 | resource_not_found | Generic not-found for non-job resources (apps, transactions, etc.). |
| 409 | conflict | Idempotency key collision, body-hash mismatch, or in-flight key reuse. See the idempotency guide. |
| 422 | validation_error | Request shape is fine but a field value is invalid (e.g. effort='warp_speed'). |
| 429 | rate_limited | Too many requests in the rate-limit window. Back off and retry. |
| 500 | internal_error | Server-side bug. Quote the request_id in a support ticket. |
| 503 | upstream_unavailable | An upstream service (data provider, MCP tool) is degraded. Usually transient — retry. |
402 insufficient_credits vs 429 rate_limited
Both prevent your request from succeeding, but they mean different things and require different handling. Don't conflate them. Your tenant has zero credits remaining. Backing off and retrying won't help — top up the account or contact your admin. The X-Grep-Credits-Remaining header will be 0. You're sending requests faster than your rate limit allows. Back off using the Retry-After header and retry. Credits aren't depleted — this is purely about request frequency.