---
title: Errors
description: HTTP status codes and the error response shape for the AgentPing API.
section: api
order: 6
---

# Errors

Consistent error envelope across every endpoint on `https://eu.ingest.agentping.io/v1/...`. Knowing which codes are retryable and which are terminal is most of what you need.

## Response shape

JSON endpoints (`POST /v1/runs`, `POST /v1/runs/{id}/events`, `POST /v1/runs/{id}/finish`, `POST /v1/heartbeats`):

```http
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "invalid_id",
  "message": "Field 'id' must match pattern run_(eu|us)_[0-9a-f]{32}.",
  "field": "id"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `error` | string | Stable machine-readable code. SDKs branch on this. |
| `message` | string | Human-readable explanation. |
| `field` | string | Present when tied to a specific request field. |

`GET /v1/ping` errors use `text/plain` with a single line; the first word is the machine code, the rest is the operator-friendly message.

## Status codes

| Status | Meaning | Retryable? |
|--------|---------|------------|
| `200 OK` | Idempotent retry: ID already stored with matching fields. | No retry needed. |
| `202 Accepted` | Payload accepted. | No retry needed. |
| `400 Bad Request` | Malformed: bad ID, missing field, unknown region, batch over 50, invalid JSON. | No. Fix the request. |
| `401 Unauthorized` | Bad credential, region mismatch, or ping-token scope violation. | No. Fix credentials or routing. |
| `409 Conflict` | Idempotent ID reused with different content. For runs: same `id`, different `agent` or `started_at`. | No. Investigate. |
| `429 Too Many Requests` | Rate-limit bucket exhausted (per-second request rate). | Yes, with back-off. See [Quotas](/docs/billing/quotas). |
| `500 Internal Server Error` | Unexpected server failure. | Yes, with backoff. |
| `502 Bad Gateway` | Edge couldn't reach worker pool. | Yes, with backoff. |
| `503 Service Unavailable` | Ingest shedding load or deploying. | Yes, with backoff. |

## Common error codes

| `error` | When | Typical fix |
|---------|------|-------------|
| `invalid_id` | `400` on malformed identifier. | Match `^<prefix>_[a-z]{2}_[0-9a-f]{32}$`. Let the SDK construct IDs. |
| `missing_field` | `400` when a required field is absent. | Add the field named in `message`. |
| `unknown_region` | `400` when region segment isn't `eu` or `us`. | Use a supported-region credential. |
| `region_mismatch` | `401` when credential region differs from edge region. | Send to the matching regional endpoint. |
| `invalid_credential` | `401` when credential is wrong, revoked, or malformed. | Issue a new credential. |
| `scope_violation` | `401` when a ping token is used for an out-of-scope agent. | Use the right token, or issue one for the new agent. |
| `id_conflict` | `409` when the same run `id` was previously stored with different fields. | Use a fresh UUIDv7. Indicates a client bug. |
| `rate_limit` | `429` from the per-team bucket. | Back off, retry after `Retry-After`. |

## Examples

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "error": "region_mismatch",
  "message": "Credential region 'us' does not match endpoint region 'eu'. Send this request to https://eu.ingest.agentping.io/v1/... from the us region, or use an eu credential."
}
```

```http
HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "error": "id_conflict",
  "message": "Run 'run_eu_018f3a2b9c1d7e8fa4b9c2d7e8f1a3b6' exists with agent 'support-triage', started_at 2026-05-15T14:32:01.123Z. Supplied agent 'invoice-sync' does not match.",
  "field": "agent"
}
```

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 7

{
  "error": "rate_limit",
  "message": "Team rate limit exceeded: 500 req/sec sustained. Retry in 7 seconds."
}
```

## Retries

The official SDKs retry the retryable codes above automatically and never throw an AgentPing failure into your code. Terminal 4xx (`400`, `401`, `409`) are not retried; fix the request. For details, see your [SDK reference](/docs/sdks/laravel).

:::tip
`409 Conflict` in production logs needs investigation. UUIDv7 has 62 bits of post-timestamp randomness; collisions are statistically impossible. A 409 almost always means a fixture is leaking from a test path into production.
:::
