docs / audit api
Audit API
One POST starts an audit of one public page and hands back a token. One GET reads the report with it. That is the whole flow — the rest of this page is detail, errors and limits.
Authentication, in one paragraph
Starting an audit returns a session token in the response body. Send it back as Authorization: Bearer <token> to read the report. It lasts seven days and is the only thing that proves a report is yours — there are no accounts and no separate API keys. Nothing else is needed: no cookie jar, no key you have to invent, no call before the call.
One caveat, on the first request only, before any token exists: send no Origin and no Sec-Fetch-* header. With neither a token nor a same-origin session those mark the call as a browser’s, and it is refused with ORIGIN_REJECTED — which is what stops a web page spending a visitor’s audit allowance. Command-line and server-side clients send neither by default. Once you hold a token the headers stop mattering.
Start to finish
two requests and a pollcurl -sS -X POST https://geo.new/api/v1/audits \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com"}'
# → 202 {"audit_id":"aud_…","session":"…","markdown_url":"…","poll_after_seconds":2}
curl -sS -H "Authorization: Bearer $SESSION" "$MARKDOWN_URL"
# → 202 while it fills, 200 once X-Audit-Status is complete, partial or failedStart an audit
no token neededPOST/api/v1/audits
Admits one audit of one public page, or attaches to a run already in flight for that URL, and hands back the token you read it with.
curl -sS -X POST https://geo.new/api/v1/audits \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com"}'- The envelope carries session, audit_id, status, revision, cached, report_url, snapshot_url, markdown_url and poll_after_seconds. Keep session: it is how you read the report back, and how you reach it again tomorrow.
- Send the same URL twice on one session and you attach to the run already in flight, or reuse a finished one under an hour old. Duplicate requests do not duplicate work, and you need no key to get that.
- Idempotency-Key is optional, 16–128 printable characters, and only earns its place on fresh: true, where that reuse is skipped. It cannot be issued by this API — it only works if it stays the same across your retries — so a client library should generate one per call and you should never have to type one.
- fresh: true skips reuse and is what TARGET_COOLDOWN limits to one run per URL per five minutes.
Read the snapshot
token requiredGET/api/v1/audits/{id}
The whole report as saved: scores, findings, evidence, crawler policy and page inventory.
curl -sS -H "Authorization: Bearer $SESSION" \ https://geo.new/api/v1/audits/$AUDIT_ID
- Always 200 while running, so read status, not the status code. It is one of queued, fetching, analyzing, writing, complete, partial or failed.
- scores.catalog carries the single GEO score: six categories with subcategories, platforms, pillars and blockers. scores.geo and scores.seo are the retained v1 summaries.
- GET never starts work and never triggers a model call. Poll it every two seconds until the status is terminal.
- The browser app authenticates with its httpOnly geo_session cookie instead, which is why that cookie exists; a script should not have to keep a jar.
Read the Markdown report
token requiredGET/api/v1/audits/{id}/report.md
The same report projected to Markdown, for pasting into a model or a pull request.
curl -sS -H "Authorization: Bearer $SESSION" \ https://geo.new/api/v1/audits/$AUDIT_ID/report.md
- Unlike the snapshot, this one does answer 202 while the report fills, so the status code is a usable signal here.
- X-Audit-Revision and X-Audit-Status describe the exact saved revision in the body you received.
- 200 means the report is finished, not that the page passed. Read X-Audit-Status.
Open a session first
no token neededPOST/api/v1/session
Mints a token on its own, for when you want the credential in hand before a request you cannot afford to send twice.
curl -sS -X POST https://geo.new/api/v1/session
- Optional, and listed last for a reason: starting an audit mints a session too, so the ordinary flow never calls this.
- It earns its place in one case. Idempotency records are stored against the session that made them, so if a create request times out before its token reaches you, the retry authenticates as somebody new and cannot bind to the original — you would start a second audit and lose the first. Holding a token beforehand removes that.
- The token lasts seven days and is the only thing that proves a report is yours. There are no accounts and no separate API keys.
Errors
every failure, one shape{"error":{"code":"TARGET_COOLDOWN",
"message":"This URL was recently audited. Wait five minutes between fresh runs.",
"retryable":true,
"request_id":"req_4c1f…",
"retry_after_seconds":300}}Amber codes are retryable and carry Retry-After; wait it out rather than backing off blindly. Every response carries request_id — quote it if you report a problem.
Limits
anonymous allowancesReusing a cached report spends a request, not an audit start. Keys with higher personal allowances are planned; they will still share the ceiling everyone shares.
Outside the contract
These routes exist because the web app uses them. They need a CSRF token, they are not in the OpenAPI contract, and they can change without notice. Do not build on them.
POST /api/session · GET /api/audits/:id/events · POST /api/audits/:id/rerun
POST /api/audits/:id/share · DELETE /api/audits/:id/shares · GET /api/shares/:token
GET /api/audits/:id/image · GET /api/audits/:id/baselines · GET /api/audits/:id/compare/:baseline