# API Errors - Grep

[Developers](/developers)[API Reference](/developers/api)Errors

# Errors

Structured error envelope and the full code registry. Plus the difference between 402 insufficient\_credits and 429 rate\_limited.

## 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.

json

```
{
  "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.

`402 insufficient_credits`

### 402 — out of credits

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.

`429 rate_limited`

### 429 — too many requests

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.

Pro tip: switch on code, not status

Two different error codes can travel on the same HTTP status (e.g. 404 covers both job\_not\_found and resource\_not\_found). Always branch on error.code in your client — it's the stable contract.
