# Errors & Limits

Standard HTTP status codes, async job envelopes, and per-key limits help you build reliable integrations.

AgentSEO uses normal HTTP status codes for request-level failures and a structured job status envelope for async work. Rate limits are split between workload traffic and higher-budget control traffic such as job polling and SSE streams.

## HTTP Status Codes

| Code | Meaning | Description |
| --- | --- | --- |
| 200 | OK | Request succeeded. Result is in the body. |
| 202 | Accepted | Job queued or still processing. Poll `/jobs/{id}` or stream `/jobs/{id}/events`. |
| 400 | Bad Request | Invalid parameters. check `details` field. |
| 401 | Unauthorized | Invalid or missing API key. |
| 402 | Payment Required | Credits are exhausted for the API key or workspace budget path in use. |
| 403 | Forbidden | Key is revoked, expired, or blocked by an allowed-domain policy. |
| 404 | Not Found | Resource or endpoint does not exist. |
| 429 | Too Many Requests | Rate limit exceeded. Read `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset` to decide when to retry. |
| 5xx | Server Error | Something went wrong while running the analysis. Retry transient failures and inspect job-level error codes on async workflows. |

## Error Response Format

Code example

```
{
  "error": {
    "code": "INVALID_PARAMETERS",
    "message": "The request parameters are invalid.",
    "details": {
      "domain": {
        "_errors": ["Domain or Business Name is required"]
      }
    },
    "request_id": "req_123xyz"
  }
}
```

Code example

```
{
  "error": {
    "code": "CREDITS_EXHAUSTED",
    "message": "Insufficient credits to run this request.",
    "details": {
      "credits_remaining": 0
    },
    "request_id": "req_456abc"
  }
}
```

## Async Job Status Format

Most workflow endpoints are async-first. A queued or in-flight job can be polled through `/jobs/{id}` or streamed through `/jobs/{id}/events`. Completed jobs return `result`; failed jobs return a structured `error` block with a retryable flag.

Code example

```
{
  "jobId": "123e4567-e89b-12d3-a456-426614174000",
  "status": "processing",
  "phase": "waiting_analysis",
  "progress": {
    "percent": 55,
    "indeterminate": true,
    "eta_seconds": 12
  },
  "attempt": 1,
  "max_attempts": 3,
  "message": "Waiting for analysis results.",
  "poll_url": "/api/v1/jobs/123e4567-e89b-12d3-a456-426614174000",
  "events_url": "/api/v1/jobs/123e4567-e89b-12d3-a456-426614174000/events",
  "retry_after_seconds": 2
}
```

Code example

```
{
  "jobId": "123e4567-e89b-12d3-a456-426614174000",
  "status": "failed",
  "phase": "finalizing",
  "progress": {
    "percent": 100,
    "indeterminate": false,
    "eta_seconds": null
  },
  "attempt": 3,
  "max_attempts": 3,
  "message": "Job failed.",
  "error": {
    "code": "UPSTREAM_FETCH_FAILED",
    "message": "UPSTREAM_FETCH_FAILED: fetch failed",
    "retryable": true
  },
  "poll_url": "/api/v1/jobs/123e4567-e89b-12d3-a456-426614174000",
  "events_url": "/api/v1/jobs/123e4567-e89b-12d3-a456-426614174000/events",
  "retry_after_seconds": 2
}
```

## Rate Limits

Limits are applied per API key. AgentSEO uses a lower budget for workload requests that create or run SEO work, and a higher budget for control-plane requests such as `/jobs/{id}` polling and `/jobs/{id}/events` SSE connections.

| Plan | Workload | Control |
| --- | --- | --- |
| Hobby | 60 / minute | 600 / minute |
| Starter | 120 / minute | 1200 / minute |
| Pro | 300 / minute | 3000 / minute |
| Agency | 1000 / minute | 10000 / minute |

On rate-limit responses, read `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset`. `Retry-After` is used on some sync-pending job responses, not as the main rate-limit signal.

---
Canonical HTML: https://www.agentseo.dev/docs/errors
Markdown: https://www.agentseo.dev/docs/errors/index.md
