---
title: "Standard SEO scan for a URL + keyword."
method: POST
path: "/v1/scan"
tags: ["Scan"]
---

# Standard SEO scan for a URL + keyword.

`POST /v1/scan`

## Request

- Base URL: `https://api.on-page.ai`
- URL: `https://api.on-page.ai/v1/scan`
- Auth: HTTP bearer
- TypeScript: <https://skmtc.dev/on-page/apis/on-page-ai-api/docs/v1/scan/post.ts> (a function with typed inputs; its response is checked against the schema)

Standard 2-credit SEO scan. Returns benchmarks, related-entity density scores, NLP-classified entities, structured-data benchmark and coverage sections, internal-link recommendations, topical-authority questions, the competitor × entity cohort matrix, and a content brief — the full `customer_v1` shape.
### Supported `region` values

Pass any `code` from this table as `region` to target that country's Google SERP. The full list is also available at `GET /v1/regions`. `googleDomain` is informational only — crawls hit `google.com` with the `gl` parameter rather than the country-specific domain.

| code | name | google domain | gl | default locale |
|------|------|---------------|----|----------------|
| `US` | 🇺🇸 United States | `google.com` | `us` | `en-US` |
| `CA` | 🇨🇦 Canada | `google.ca` | `ca` | `en-CA` |
| `UK` | 🇬🇧 United Kingdom | `google.co.uk` | `gb` | `en-GB` |
| `AU` | 🇦🇺 Australia | `google.com.au` | `au` | `en-AU` |
| `NZ` | 🇳🇿 New Zealand | `google.co.nz` | `nz` | `en-NZ` |
| `ES` | 🇪🇸 Spain | `google.es` | `es` | `es-ES` |
| `DE` | 🇩🇪 Germany | `google.de` | `de` | `de-DE` |
| `IT` | 🇮🇹 Italy | `google.it` | `it` | `it-IT` |
| `FR` | 🇫🇷 France | `google.fr` | `fr` | `fr-FR` |
| `IE` | 🇮🇪 Ireland | `google.ie` | `ie` | `en-IE` |
| `NL` | 🇳🇱 Netherlands | `google.nl` | `nl` | `nl-NL` |
| `CH` | 🇨🇭 Switzerland | `google.ch` | `ch` | `de-CH` |
| `SE` | 🇸🇪 Sweden | `google.se` | `se` | `sv-SE` |
| `NO` | 🇳🇴 Norway | `google.no` | `no` | `nb-NO` |
| `DK` | 🇩🇰 Denmark | `google.dk` | `dk` | `da-DK` |
| `FI` | 🇫🇮 Finland | `google.fi` | `fi` | `fi-FI` |
| `ZA` | 🇿🇦 South Africa | `google.co.za` | `za` | `en-ZA` |
| `MX` | 🇲🇽 Mexico | `google.com.mx` | `mx` | `es-MX` |
| `BR` | 🇧🇷 Brazil | `google.com.br` | `br` | `pt-BR` |
| `CO` | 🇨🇴 Colombia | `google.com.co` | `co` | `es-CO` |
| `IN` | 🇮🇳 India | `google.co.in` | `in` | `en-IN` |
| `SG` | 🇸🇬 Singapore | `google.com.sg` | `sg` | `en-SG` |
| `MY` | 🇲🇾 Malaysia | `google.com.my` | `my` | `ms-MY` |
| `JP` | 🇯🇵 Japan | `google.co.jp` | `jp` | `ja-JP` |
| `KE` | 🇰🇪 Kenya | `google.co.ke` | `ke` | `en-KE` |
| `AE` | 🇦🇪 UAE | `google.ae` | `ae` | `ar-AE` |
| `HK` | 🇭🇰 Hong Kong | `google.com.hk` | `hk` | `zh-HK` |

## Headers

- `Idempotency-Key` string

## Request body

- object: Scan a URL + keyword combo. `region` selects the Google SERP country (27 supported — see GET /v1/regions). `market` is a deprecated alias for `region`; sending both with different values returns `REGION_MISMATCH`. `compatibility_mode` can be set to true for slower, more tolerant competitor crawling; target URL recovery is automatic either way. `callbackMetadata` (if present) is echoed back in any webhook deliveries for this job.
  - `url` string, uri
  - `keyword` string, required: Primary search keyword. Whitespace is normalized. Must be a concise search query: max 150 characters.
  - `region` 'US' | 'CA' | 'UK' | 'AU' | 'NZ' | 'ES' | 'DE' | 'IT' | 'FR' | 'IE' | 'NL' | 'CH' | 'SE' | 'NO' | 'DK' | 'FI' | 'ZA' | 'MX' | 'BR' | 'CO' | 'IN' | 'SG' | 'MY' | 'JP' | 'KE' | 'AE' | 'HK': Region code. Case-insensitive and trimmed on input. Use canonical uppercase codes in response bodies. See GET /v1/regions.
  - `market` 'US' | 'CA' | 'UK' | 'AU' | 'NZ' | 'ES' | 'DE' | 'IT' | 'FR' | 'IE' | 'NL' | 'CH' | 'SE' | 'NO' | 'DK' | 'FI' | 'ZA' | 'MX' | 'BR' | 'CO' | 'IN' | 'SG' | 'MY' | 'JP' | 'KE' | 'AE' | 'HK': DEPRECATED: use `region`. Accepted for backward compatibility; the API emits `Deprecation: true` + `Link` response headers when this field is used. Must match `region` if both are sent.
  - `locale` string: BCP-47 locale tag. Canonicalized via Intl.Locale (`en_us` → `en-US`; `zh_hant_hk` → `zh-Hant-HK`). Max 16 chars.
  - `compatibility_mode` boolean: Optional. Defaults to false. When true, allows budgeted browser rendering for difficult competitor pages. The target URL already receives automatic recovery when needed.
  - `webhook` object
    - `url` string, uri, required
    - `events` string[]
  - `callbackMetadata` object

## Response `202`

Accepted

- AcceptedJobResponse: Response to a successful `POST /v1/classify|scan|scan/lite|scan/deep` job-create call. Returned as HTTP 202. Use `jobId` as the canonical job identifier in generated SDKs; the live REST response also includes a deprecated `job_id` alias for backward compatibility.
  - `jobId` string, required
  - `poweredBy` 'On-Page.ai', required
  - `status` 'queued', required
  - `request_id` string, required
  - `estimated` object
    - `queue_position` integer, required

## Other responses

- `400`: Validation error
- `401`: Unauthorized
- `402`: Insufficient credits
- `403`: Scope forbidden
- `409`: Idempotency conflict
- `422`: URL not allowed
- `429`: Too many requests. Body is a `oneOf` of rate-limit / org-active-limit / org-queued-limit / scan-queue-saturated variants — branch on `error.code`. `Retry-After` is present when the limiter emits it; `X-RateLimit-*` is present on rate-limit-middleware 429s; `X-Concurrency-*` is present on concurrency-limit 429s (ORG_ACTIVE_LIMIT_REACHED / ORG_QUEUED_LIMIT_REACHED).
- `500`: Internal error

---

[API](https://skmtc.dev/on-page/apis/on-page-ai-api.md) - [All operations](https://skmtc.dev/on-page/apis/on-page-ai-api/llms.txt) - [OpenAPI document](https://skmtc.dev/on-page/apis/on-page-ai-api/revisions/a081e8701c19?raw)
