REST API Reference
All analysis endpoints are async — submit a job, poll for progress, then fetch the result. Every request needs a Bearer API key.
Base URL
https://api.on-page.ai
Endpoints
/v1/classify0.2 creditsCategorize a page or text into 1,091 semantic categories.
Provide at least one of text or url. languageHint is optional.
Request
{
"text": "optional raw text (max 20,000 chars)",
"url": "https://example.com/article",
"languageHint": "en"
}Response
{
"job_id": "job_...",
"status": "queued",
"request_id": "req_...",
"estimated": { "queue_position": 1 }
}/v1/scan2 creditsRun a standard SEO scan against top Google results for a keyword.
Takes 30s-2min. Use for most SEO optimization tasks. Reports include related-entity density scores that compare the analyzed URL's important-entity signal against the competitor cohort. `keyword` is normalized for whitespace and capped at 150 characters; `url` is capped at 2048 characters. `region` selects the Google SERP country (27 supported — see GET /v1/regions or the in-operation table in the OpenAPI reference). `market` is a deprecated alias for `region`; if you send it the response carries `Deprecation: true` + `Link` headers — please migrate. `compatibility_mode` (default `false`) is an advanced flag that runs the slower compatibility crawl path against every ranking competitor — significantly slower scans, but higher coverage when the SERP is dominated by sites with strong bot protection (Cloudflare, Akamai, etc.). The target URL already receives maximum-compatibility crawling automatically regardless of this flag.
Request
{
"url": "https://example.com/page",
"keyword": "target keyword",
"region": "US",
"locale": "en-US",
"compatibility_mode": false
}Response
{
"job_id": "job_...",
"status": "queued",
"request_id": "req_...",
"estimated": { "queue_position": 0 }
}/v1/scan/lite1.5 creditsQuick SEO scan focused on entity coverage and competitor cohort analysis — ideal for fast audits and bulk workflows. Returns benchmarks + entities + highly related words + cohort analysis. Skips page classification, internal link recommendations, and topical-authority questions.
Same inputs as /v1/scan, including the optional `compatibility_mode` flag (default `false`; see /v1/scan notes). Result payload follows the customer_v1_lite shape (see /docs/report-schema) and includes related-entity density scores. Requesting response_format=customer_v1 on a Lite job is rejected with 409 UNSUPPORTED_FORMAT — use customer_v1_lite or omit the parameter.
Request
{
"url": "https://example.com/page",
"keyword": "target keyword",
"region": "US",
"locale": "en-US",
"compatibility_mode": false
}Response
{
"job_id": "job_...",
"status": "queued"
}/v1/scan/deep3 credits50% deeper than Standard with 15 competitors. Deep responses may also include a SERP-speed benchmark — head-to-head loading speed and visual stability of your page vs the top 3 organic competitors in the same SERP.
Same inputs as /v1/scan, including the optional `compatibility_mode` flag (default `false`; see /v1/scan notes). Takes 1–3 min. The terminal report (GET /v1/jobs/:id/result) carries the standard customer_v1 sections, including related-entity density scores, and Deep responses MAY also include the optional `serp_speed_benchmark` field — page-experience metrics (LCP, CLS, approximate TBT, TTFB; FCP at per-probe level) for the target page and the top 3 organic competitor URLs, measured under identical lab conditions. Lite and Standard never include this field; clients must check for presence. Self-hosted; never calls Google PageSpeed Insights, CrUX, or Lighthouse. See the OpenAPI reference at /reference (`serp_speed_benchmark` schema) for the field shape and the per-probe `status` enum.
Request
{
"url": "https://example.com/page",
"keyword": "target keyword",
"region": "US",
"locale": "en-US",
"compatibility_mode": false
}Response
{
"job_id": "job_...",
"status": "queued"
}/v1/jobs/:jobIdFreePoll job status and progress.
Status values: "queued", "running", "completed", "failed". `region` + `locale` echo the canonical scan region (always `null` for classify jobs and for legacy scan rows that pre-date region support).
Request
No request body.Response
{
"job_id": "job_...",
"type": "scan",
"status": "running",
"progress": 45,
"created_at": "2026-04-14T...",
"started_at": "2026-04-14T...",
"completed_at": null,
"region": "UK",
"locale": "en-GB",
"result": null,
"error": null
}/v1/jobs/:jobId/resultFreeFetch the full report for a completed job.
Returns the customer_v1 format by default. Lite and Standard responses never carry `serp_speed_benchmark`; Deep responses MAY include it (optional field — present when the benchmark produced a payload). Clients should check for presence on Deep results. See Report Schema docs for field details.
Request
No request body.Response
{
"schema_version": "onpage-report-customer-v1",
"meta": { "report_date": "...", "target_keyword": "..." },
"benchmarks": { "page1_average": {...}, "your_url": {...} },
"entity_coverage": { ... },
"topic_and_classification": { ... },
"internal_linking": { ... },
"competitor_term_coverage": { ... },
"serp_speed_benchmark": { "status": "ok", ... } // Deep only, optional
}/v1/creditsFreeCheck available and reserved credit balances.
Use available_credits and reserved_credits for normal display. Reserved = credits held for in-progress jobs. The millicredit fields remain for existing integrations.
Request
No request body.Response
{
"available_credits": 9531,
"reserved_credits": 48,
"available_millicredits": 9531000,
"reserved_millicredits": 48000
}/v1/regionsFree (public)List supported scan regions for the `region` field.
27 supported regions. Cached for 24 hours (`Cache-Control: public, max-age=86400`). Use the `code` value as `region` on POST /v1/scan*. `googleDomain` is informational — crawls hit `google.com` with the `gl` parameter rather than the country-specific domain.
Request
No request body. No authentication required.Response
{
"defaultRegion": "US",
"regions": [
{ "code": "US", "name": "United States", "flagEmoji": "🇺🇸",
"googleDomain": "google.com", "googleGl": "us", "defaultLocale": "en-US" },
{ "code": "UK", "name": "United Kingdom", "flagEmoji": "🇬🇧",
"googleDomain": "google.co.uk", "googleGl": "gb", "defaultLocale": "en-GB" }
/* …25 more rows… */
]
}/v1/webhooks/testFreeQueue a test webhook delivery to verify your endpoint.
The signing secret is returned so you can verify the signature.
Request
{
"url": "https://your-app.example/webhooks",
"event": "job.completed"
}Response
{
"delivery_id": "whd_...",
"status": "queued",
"signing_secret": "hex..."
}Code examples
curl
curl https://api.on-page.ai/v1/scan \
-X POST \
-H "Authorization: Bearer op_live_your_key" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","keyword":"example keyword","region":"US"}'TypeScript
const res = await fetch("https://api.on-page.ai/v1/scan", {
method: "POST",
headers: {
Authorization: "Bearer op_live_your_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com",
keyword: "example keyword",
region: "US",
}),
});
const { job_id } = await res.json();Python
import requests
resp = requests.post(
"https://api.on-page.ai/v1/scan",
headers={"Authorization": "Bearer op_live_your_key"},
json={"url": "https://example.com", "keyword": "example keyword", "region": "US"},
)
print(resp.json()["job_id"])Error codes
All errors return { "error": { "code": "...", "message": "..." } } with an appropriate HTTP status code.
| Code | Description |
|---|---|
INVALID_API_KEY | Bearer token missing, malformed, or not valid. |
VALIDATION_ERROR | Request body or query failed schema validation. |
REGION_VALIDATION_ERROR | Unknown region or market value. Response includes error.supported_regions listing the 27 valid codes (also at GET /v1/regions). |
REGION_MISMATCH | Both `region` and `market` were sent with different values. Use `region` only — `market` is a deprecated alias. |
INSUFFICIENT_CREDITS | Not enough credits for this action. |
RATE_LIMITED | Rate limit exceeded. Check Retry-After header. |
ORGANIZATION_SUSPENDED | Organization suspended — contact support. |
ORG_ACTIVE_LIMIT_REACHED | Your org already has 5 jobs running. Wait for one to finish. |
ORG_QUEUED_LIMIT_REACHED | Your org has 100 jobs waiting. Wait for the queue to drain. |
SCAN_QUEUE_SATURATED | Global scan capacity full. Retry later. |
RESULT_NOT_READY | Job hasn't produced a result yet. |
URL_NOT_ALLOWED | URL failed safety checks. |
NOT_FOUND | Job or resource doesn't exist. |
IDEMPOTENCY_CONFLICT | Same key reused with different payload. |