Skip to content

For developers

API

The API does two things: it starts a scan of a site in your account, and it reads what your account can read in the app — sites, scan status and reports. It is part of the Gold and Platinum plans.

Authentication

  • Create a key in the app under Settings → API. It is shown once; we keep a fingerprint of it, not the key.
  • Send it with every request as a bearer token. Cookies are ignored.
  • A key has one or both scopes: reports:read (sites, scan status, reports) and scans:create (start scans).
  • A key is not a login. It cannot change your account, your sites, your billing or your keys. If one leaks, revoke it — the others keep working.

All paths are under https://app.geoready.org/api. A machine-readable description is at https://app.geoready.org/api/v1/openapi.json.

Endpoints

RequestScopeWhat it does
GET /v1/me—Your plan, this week's quota, and the key making the request.
GET /v1/sitesreports:readThe sites in your account.
POST /v1/scansscans:createStart a scan of one of those sites.
GET /v1/scans/{jobId}reports:readStatus of a scan your account started.
GET /v1/reportsreports:readThe latest finished scans of each site — up to 8 per site, not the full history.
GET /v1/reports/{jobId}reports:readOne report, with the same content and the same level of detail you see in the app. Add ?locale=zh for Chinese.

GET /v1/me

curl https://app.geoready.org/api/v1/me -H "Authorization: Bearer geoready_…"

# 200
{
  "plan": "gold",
  "quota": { "limit": 260, "remaining": 259, "windowStart": "2026-10-08T00:00:00.000Z" },
  "key": { "name": "Nightly report job", "scopes": ["reports:read", "scans:create"] }
}

GET /v1/sites

curl https://app.geoready.org/api/v1/sites -H "Authorization: Bearer geoready_…"

# 200
{ "sites": [ { "siteKey": "example.com", "role": "OWNED" }, { "siteKey": "example.org", "role": "COMPETITOR" } ] }

POST /v1/scans

curl -X POST https://app.geoready.org/api/v1/scans \
  -H "Authorization: Bearer geoready_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: nightly-2026-10-13" \
  -d '{"domain": "example.com"}'

# 202 — a new scan; 200 with "replayed": true when this Idempotency-Key was used before
{
  "jobId": "0199d2c4-7c1e-7a55-9c7d-3f0f1c2b4a10",
  "siteKey": "example.com",
  "replayed": false,
  "quota": { "limit": 260, "remaining": 259, "windowStart": "2026-10-08T00:00:00.000Z" }
}

GET /v1/scans/{jobId}

curl https://app.geoready.org/api/v1/scans/0199d2c4-7c1e-7a55-9c7d-3f0f1c2b4a10 -H "Authorization: Bearer geoready_…"

# 200 — status is PENDING, RUNNING, SUCCEEDED, INCOMPLETE or FAILED
{
  "jobId": "0199d2c4-7c1e-7a55-9c7d-3f0f1c2b4a10",
  "siteKey": "example.com",
  "status": "SUCCEEDED",
  "createdAt": "2026-10-13T02:00:00.000Z",
  "finishedAt": "2026-10-13T02:00:41.000Z"
}

GET /v1/reports

curl https://app.geoready.org/api/v1/reports -H "Authorization: Bearer geoready_…"

# 200 — baseScore is null when a scan produced no score
{
  "sites": [
    {
      "siteKey": "example.com",
      "role": "OWNED",
      "reports": [
        { "jobId": "0199d2c4-…", "status": "SUCCEEDED", "checkedAt": "2026-10-13T02:00:41.000Z", "baseScore": 72, "pagesLimit": 10 }
      ]
    }
  ]
}

GET /v1/reports/{jobId}

curl "https://app.geoready.org/api/v1/reports/0199d2c4-7c1e-7a55-9c7d-3f0f1c2b4a10?locale=en" -H "Authorization: Bearer geoready_…"

# 200 — abridged: every check row has id, displayClass, goal, outcome, evidence, fix, prompt
{
  "reportId": "0199d2c4-…",
  "siteKey": "example.com",
  "checkedAt": "2026-10-13T02:00:41.000Z",
  "complete": true,
  "baseScore": 72,
  "coverage": 1,
  "fullAccess": true,
  "baseModules": [ { "id": "F08", "displayClass": "scored-fail", "verdict": "fail", "kind": "scoring", "goal": "…", "outcome": "…", "evidence": [], "fix": "…", "prompt": "…" } ],
  "agentModules": [ … ],
  "extraModules": [ … ],
  "paid": { "paidScore": 90, "pagesSampled": 10, "modules": [ … ] },
  "ai": []
}

# 409 while the scan has no finished report
{ "code": "report-not-ready", "message": "…", "details": { "status": "RUNNING" } }

Starting a scan

  • The site must already be in your account's list. The API never adds a site.
  • Each scan uses one unit of your weekly quota — the same quota the app uses. There is no separate API quota, and the API cannot start free checks.
  • The response has the scan's id and your quota as it is now. Poll the status endpoint until it is SUCCEEDED, INCOMPLETE or FAILED, then read the report.

Repeating a request safely

  • Send an Idempotency-Key header (8–128 printable ASCII characters) to make a request safe to retry.
  • Repeating a request with the same key and the same domain returns the first scan with "replayed": true and status 200. Nothing is debited — also when your quota has run out since, the site was removed from your list, or that scan failed. To scan again, use a new key.
  • The same key with a different domain is refused (409).
  • A key belongs to the API key that sent it: two API keys can use the same value independently.
  • There is no fixed lifetime: a key is remembered as long as its scan exists.
  • After your plan ends, a replay is refused like any other request (403).

Errors

Every error has the same shape: a stable code, a message for people, and sometimes details.

{ "code": "quota-exhausted", "message": "…", "details": { "quota": { "limit": 260, "remaining": 0, "windowStart": "…" } } }
StatusCodeWhen
400invalid-requestThe body, a parameter or the Idempotency-Key header is malformed.
400invalid-domainThe domain cannot be understood.
401unauthorizedNo key, or a key that is unknown, revoked or expired. The answer is the same in each case.
403api-not-in-planYour plan does not include the API (or has ended).
403insufficient-scopeThe key lacks the scope this request needs.
403quota-exhaustedThis week's quota is used up.
404not-foundNo such path, scan or report — or not yours to read.
404site-not-trackedThe domain is not in your account's site list.
409idempotency-conflictThe Idempotency-Key was used for another domain.
409report-not-readyThe scan has no finished report yet; details.status says where it is.
429rate-limitedToo many requests. Retry-After says when to try again.
500internal-errorSomething went wrong on our side.

Webhooks

Webhooks are not switched on for this service yet. Poll the scan status endpoint for now.

Limits

  • 60 requests a minute per key, 120 a minute per account, 300 a minute per address.
  • 10 scan starts a minute per key.
  • 30 requests a minute with a missing or wrong key per address.
  • Over a limit you get 429 with a Retry-After header.

What the API does not do

  • Free checks, and scans of sites that are not in your list.
  • Adding or removing sites, billing, account settings, key management.
  • Starting AI analyses or changing whether a report is shared.