The GeoScored API is a REST API that returns JSON. Every request must include an API key header.
You can generate keys in Account Settings → API Keys.
Base URL
https://app.geoscored.ai/api/v1
Content type
application/json
The interactive OpenAPI specification is available at
/api/docs (Swagger UI) and
/api/redoc (ReDoc).
This page covers the concepts and patterns that the auto-generated docs do not.
2. Authentication
Every request requires an API key passed in the X-API-Key header.
Keys are scoped to your organization. All members of an org share the same scan history.
# Pass your API key on every request
curl https://app.geoscored.ai/api/v1/scans \
-H "X-API-Key: geo_your_key_here" \
-H "Content-Type: application/json"
Key format
All keys begin with geo_. Requests with missing or malformed keys return 401.
Org-scoped
API keys are scoped to your organization. Scans created by one key are visible to all keys in the same org.
3. Scan lifecycle
Scans are asynchronous. Creating a scan returns immediately with status pending.
The engine processes the scan in the background. Poll the scan endpoint or use a webhook callback to detect completion.
pendingCreated, queued
runningEngine active
completedfailedTerminal states
Polling pattern
Poll GET /api/v1/scans/{'{'}scan_id{'}'} until status is
COMPLETED or FAILED.
Most scans complete within 60–120 seconds. Use an exponential backoff starting at 5 seconds.
Webhook alternative
Pass a callback_url when creating a scan to receive a POST notification when the scan completes.
See the Webhooks section for payload details.
4. Endpoints
All endpoints require the X-API-Key header.
Method
Path
Description
POST
/api/v1/scans
Create a scan.
GET
/api/v1/scans
List scans. Paginated, filterable.
GET
/api/v1/scans/compare
Compare two scans with computed deltas.
GET
/api/v1/scans/{'{scan_id}'}
Get a scan by ID (status + scores).
GET
/api/v1/scans/{'{scan_id}'}/report
Full report with all check results.
GET
/api/v1/scans/{'{scan_id}'}/triage
Site Triage dossier: platform, complexity, risk lenses, evidence.
GET
/api/v1/scans/{'{scan_id}'}/checks
List all check results for a scan.
GET
/api/v1/scans/{'{scan_id}'}/checks/{'{check_id}'}
Get a single check result.
GET
/api/v1/scans/price
Pre-flight pricing check for a URL.
GET
/api/v1/usage
Current daily usage quota information.
PATCH
/api/v1/scans/{'{scan_id}'}
Update scan metadata (project, tracked URL).
DELETE
/api/v1/scans/{'{scan_id}'}
Delete a scan and all its records.
GET
/api/v1/scans/export
Export scan results as CSV.
GET
/api/v1/scans/{'{scan_id}'}/pdf
Download the PDF report for a completed scan.
POST
/api/v1/schedules
Create a recurring scan schedule.
GET
/api/v1/schedules
List all schedules for your organization.
GET
/api/v1/schedules/{'{schedule_id}'}
Get a single schedule by ID.
PATCH
/api/v1/schedules/{'{schedule_id}'}
Update frequency, notification, or active state.
DELETE
/api/v1/schedules/{'{schedule_id}'}
Deactivate a schedule (soft delete).
POST/api/v1/scansRequires subscription
Create a scan
Submits a URL for a GEO scan. The scan starts asynchronously. Returns 201 with the scan object in pending status.
Requires an active subscription.
Request body
Field
Type
Required
Description
url
string
Yes
The URL to scan. Must be publicly accessible HTTPS.
brand_name
string
No
Brand name for AI mention checks. Defaults to the domain name.
callback_url
string
No
HTTPS URL to POST scan results to when complete. Max 2048 chars.
Returns the complete report for a completed scan: search, visibility, and trust scores with grades, pillar scores, all check results, and AI-generated summaries.
Only available once the scan status is COMPLETED.
Query parameters
Parameter
Type
Default
Description
audience
string
marketing
marketing or engineering. Switches explanatory text in why_it_matters, grade_descriptor, and pillar descriptions. Scores are identical for both.
Returns the Site Triage reconnaissance dossier for a completed scan: platform detection (CMS, framework, page builder),
complexity assessment, a plain-language summary, risk lenses, a full evidence ledger, priority actions, and caveats.
Triage is a what-is-this-site-made-of dossier, not a fix list.
Returns 404 if triage has not yet been computed for the scan.
Response fields
Field
Type
Description
scan_id
string
UUID of the scan this triage belongs to.
schema_version
string
Triage schema version (e.g. "1.0"). Increment when shape changes.
Findings with status "strength" from the evidence ledger.
risk_lenses
array
Risk assessments across named dimensions. Each item: lens_id, label, bucket ("low" | "moderate" | "high" | "critical"). Raw float scores are internal-only and never returned.
evidence_ledger
array
All findings across all categories. Each item: id, category, label, status, impact, effort, why_it_matters.
caveats
array<string>
Confidence caveats and data-quality notes for this dossier.
quick_win_count
integer
Number of priority actions flagged as quick wins (high impact, low effort).
{
"scan_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"schema_version": "1.0",
"platform": {
"cms": "WordPress",
"page_builder": "Elementor",
"js_framework": null,
"complexity_bucket": "moderate"
},
"summary_paragraph": "A WordPress site built with Elementor. Moderate complexity with standard plugin overhead.",
"priority_actions": [
{
"id": "finding-001",
"label": "Enable server-side caching to reduce TTFB",
"impact": "high",
"effort": "low",
"quick_win": true
}
],
"strengths": [
{
"id": "finding-010",
"category": "structured_data",
"label": "Schema markup present on key pages",
"status": "strength",
"impact": null,
"effort": null,
"why_it_matters": "Structured data improves AI parsing and rich result eligibility."
}
],
"risk_lenses": [
{ "lens_id": "visibility", "label": "Search Visibility", "bucket": "moderate" },
{ "lens_id": "maintenance", "label": "Maintenance Burden", "bucket": "high" },
{ "lens_id": "vendor_complexity", "label": "Vendor Complexity", "bucket": "moderate" }
],
"evidence_ledger": [/* array of all finding objects */],
"caveats": [
"Platform detection is heuristic-based; verify CMS version independently."
],
"quick_win_count": 1,
"action_count": 3
}
Error: triage not yet available
# HTTP 404
{
"error": {
"type": "not_found",
"code": "triage_not_available",
"message": "Triage data not available for this scan.",
"param": "scan_id"
}
}
GET/api/v1/scans/compare
Compare two scans
Returns a structured comparison of two scans with computed deltas for each ring score (search, visibility, trust), each pillar, and each individual check.
Both scans must belong to your organization. Useful for tracking improvement over time.
Returns the current daily usage quota information for your organization: how many scans your plan allows per day,
how many have been used today, and how many remain.
Scan schedules run GEO audits on a recurring basis without manual intervention. Each schedule targets one root domain and runs monthly or quarterly. When a scan completes, GeoScored sends an email notification to the schedule owner (opt-in, default on).
Schedule limits: Free accounts can keep 5 active schedules per organization; GeoScored Pro accounts can keep 50. An org-level override applies if one is configured. Only root domains can be scheduled; subpage URLs return not_homepage.
POST/api/v1/schedules
Create a schedule
Creates a recurring scan schedule for a root domain. Returns the existing schedule (HTTP 200) if one already exists for that URL and organization, so the caller can PATCH it instead.
Request body
Field
Type
Required
Description
url
string
Yes
Root domain to scan. Must be a homepage (e.g. https://example.com). Subpages return not_homepage.
frequency
string
No
monthly or quarterly. Defaults to quarterly. Other values return invalid_frequency.
brand_name
string
No
Brand name for AI mention checks. Defaults to the domain name.
notify_email
boolean
No
Send an email when each scheduled scan completes. Defaults to true.
Only root domains can be scheduled. Enter your main domain (e.g. example.com), not a subpage.
422
invalid_frequency
Frequency must be monthly or quarterly.
422
schedule_limit_reached
Active schedule count is at the organization limit. Deactivate an existing schedule to create a new one.
404
schedule_not_found
Schedule does not exist or belongs to a different organization.
6. Error handling
All errors return a structured JSON envelope with a consistent shape. Rather than inspecting HTTP status codes alone, always read the error.code field for machine-readable error classification.
{
"error": {
"type": "invalid_request",
"code": "invalid_url",
"message": "The URL must be a publicly accessible HTTPS address.",
"param": "url"
}
}
Common error codes
HTTP
code
Meaning
401
api_key_required
No X-API-Key header present.
401
api_key_invalid
Key does not exist, is revoked, or has wrong format.
402
subscription_required
No active subscription or daily scan limit reached.
404
scan_not_found
Scan does not exist or belongs to a different org.
400
scan_not_complete
Report or checks requested before scan has finished.
422
invalid_url
URL is not a valid public HTTPS address.
422
invalid_callback_url
Callback URL must be HTTPS and publicly accessible.
429
org_rate_limit_exceeded
Organization-level rate limit reached. Back off and retry.
500
internal_error
Unexpected server error. Retrying usually resolves transient failures.
7. Rate limits
The API enforces two layers of rate limiting: IP-level limits applied before authentication, and organization-level limits applied after authentication.
Organization limits are configurable per org and may differ from the defaults shown here.
Limit group
Applies to
Headers
api_write
POST /api/v1/scans
X-RateLimit-*
api_read
All other GET endpoints
X-RateLimit-*
Rate limit information is returned in response headers:
429 responses include a Retry-After header with the number of seconds to wait before retrying.
Do not retry immediately on 429. Back off exponentially.
8. Webhooks
Pass a callback_url when creating a scan to receive a POST request when the scan completes.
The callback URL must be HTTPS and publicly reachable from the internet.
Callback payload
When a scan reaches a terminal state (COMPLETED or FAILED),
GeoScored sends a POST request to your callback URL with the scan object as the body.
Retry behavior: If your endpoint returns a non-2xx response, GeoScored retries up to 3 times with exponential backoff (5s, 25s, 125s). Your endpoint should respond within 10 seconds to avoid timeout.
9. Python examples
These examples use httpx, a modern HTTP client for Python. Install it with pip install httpx.