# SEO audit API

> SEO audit API reference: run page audits, AI crawler checks, schema validation, and site crawls over REST with an API key. Findings return with evidence.

Canonical: https://crawlwise.site/docs/api

The Crawlwise SEO audit API runs page audits, site crawls, and the AI crawler, schema, and vitals tools over REST. Programmable audits, crawls and tools. Every response includes x-credits-charged and x-credits-remaining. Errors are { error: { code, message, docs_url } }. Cursor pagination, never offset. Spec: [/v1/openapi.json](https://crawlwise.site/v1/openapi.json).

## Auth

Authorization: Bearer cw_live_… Keys live at [/account/api](https://crawlwise.site/account/api). Using this from an agent? Start at [/agents](https://crawlwise.site/agents). From a terminal: `npx crawlwise audit` calls this API; `npx crawlwise ai-crawlers` uses the free tool endpoints and needs no key. Starter and the free plan cannot create keys; the API starts at Growth, $49.99/mo.

## Errors

Every error is `{ error: { code, message, docs_url } }`. The message is written to be shown to a person as it is.

| Code | Status | What to do |
| --- | --- | --- |
| `UNAUTHORIZED` | 401 | Send Authorization: Bearer cw_live_… with a key from /account/api. A revoked key also answers 401. |
| `PLAN_REQUIRED` | 403 | The API starts at Growth. Upgrade at /plans. |
| `FORBIDDEN` | 403 | The key lacks the scope this path needs. Create a key with that scope. |
| `ACCOUNT_HOLD` | 403 | The account is on hold. Contact support. |
| `LIMIT` | 403 | A plan limit, such as the number of sites or crawl pages. The message names it. |
| `NO_CREDITS` | 402 | Not enough credits for the estimate. Top up, or estimate a smaller job with POST /v1/estimate. |
| `RATE_LIMITED` | 429 | This key has used its calls for today. Wait for Retry-After (seconds), or use another key. |
| `TOO_MANY_RUNNING` | 429 | Too many audits or crawls are running on this account. Wait for one to finish. |
| `CONCURRENT` | 409 | A crawl is already running for this site. |
| `RUNNING` | 409 | The job is still running. Poll it until status is complete. |
| `NOT_RUNNING` | 409 | The job is not running, so it cannot be cancelled or continued. |
| `INVALID` | 400 | The request body is not valid. The message says which field. |
| `EMPTY` | 400 | A required value was empty. |
| `TOOL_FAILED` | 400 | The tool could not run on that input: a private or malformed URL, a page that did not answer, or nothing to validate. The message says which. |
| `NOT_FOUND` | 404 | No such path, or no such audit, crawl or site on this account. |
| `UNKNOWN_TOOL` | 404 | No tool by that name. |
| `NO_PRICE` | 404 | That operation has no price, so it cannot be run or estimated. |
| `METHOD` | 405 | Use the method listed for the path below. |
| `CREDITS` | 503 | Credits could not be reserved. Retry in a moment; nothing was charged. |
| `JOB` | 503 | The job could not be started. Retry in a moment; nothing was charged. |
| `CRAWL` | 503 | The crawl could not be started. Retry in a moment; nothing was charged. |
| `INTERNAL` | 500 | Something failed on our side. Retry, and contact support if it keeps happening. |

## Credits

Headers `x-credits-charged` and `x-credits-remaining` are on every response. POST /v1/estimate never charges and never creates a job.

## Webhooks

HMAC-SHA256 over `id.timestamp.body`, headers webhook-id, webhook-timestamp, webhook-signature: v1,<base64>. Same scheme as Dodo.

## Paths

| Path | Methods |
| --- | --- |
| `/v1/audits` | POST, GET |
| `/v1/audits/{id}` | GET |
| `/v1/crawls` | POST |
| `/v1/crawls/{id}` | GET |
| `/v1/crawls/{id}/pages` | GET |
| `/v1/crawls/{id}/links` | GET |
| `/v1/sites` | GET, POST |
| `/v1/sites/{id}` | GET, PATCH |
| `/v1/tools/ai-crawlers` | POST |
| `/v1/tools/agent-readiness` | POST |
| `/v1/tools/bot-verify` | POST |
| `/v1/tools/schema-validate` | POST |
| `/v1/tools/vitals` | POST |
| `/v1/credits` | GET |
| `/v1/estimate` | POST |
| `/v1/openapi.json` | GET |
