API reference
Address validation and autocomplete through one API. Version 1.0.0, generated from the OpenAPI 3.1 specification.
OpenAPI (YAML)
The machine-readable source of this page.
OpenAPI (JSON)
Same spec, JSON encoded.
Postman collection
Every endpoint, ready to send.
Base URL
https://geoverdict.com
Authentication
Secret API key
Authorization: Bearer ak_live_... or ak_test_...
Secret server-side API key, created in the GeoVerdict console. Format ak_(live|test)_<base64url>. Send as Authorization: Bearer ak_live_.... ak_test_ keys call real providers with a separate 5,000-credit daily allowance and best-effort 60-request/minute limit; they do not consume monthly credits.
Publishable widget token
Authorization: Bearer gv_pk_live_... or gv_pk_test_...
Project-scoped publishable widget token with an exact HTTPS origin allowlist. Only valid on /v1/widget/autocomplete. Safe to expose in browser code; cannot call any other endpoint.
Endpoints
- GET
/healthz - POST
/v1/validate - POST
/v1/autocomplete - POST
/v1/widget/autocomplete -
/v1/widget/autocomplete - GET
/v1/usage - POST
/demo/validate
GET/healthz
Service health
Returns service status and the ids of the currently active geocoding providers. Free, unauthenticated.
Responses
| Status | Body | Description |
|---|---|---|
200 |
object | Service is up. |
200 example
{
"status": "ok",
"providers": [
"bag"
]
}POST/v1/validate
Validate an address
Validates a single address and returns a verdict, a 0-100 confidence score, the standardized address, and per-component comparison results. Provide either a free-form query or structured components (at least one is required).
Costs 1 credit per provider that answered; a cache hit or a request where no provider answers costs 0.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
source |
query | string length 0–64 |
Fallback for the source body field; the body value wins when both are set. |
Request body
Provide query, components, or both. At least one is required.
| Field | Type | Description |
|---|---|---|
query |
string length 1–500 |
Free-form address text. |
components |
AddressComponents | Structured address parts. All fields optional. |
country |
string length 2–2 |
ISO 3166-1 alpha-2 country bias/filter. Providers that do not cover this country are skipped. |
language |
string length 2–8 |
Preferred response language (e.g. en, nl-NL). |
source |
string length 1–64 |
Free-form tag for segmenting usage per feature or system (e.g. checkout). Appears in /v1/usage and the console. |
debug |
boolean | When true, the response additionally includes candidates (all scored provider results) and trace (the provider routing trail). |
Free-form query
{
"query": "Prinsengracht 263, Amsterdam",
"country": "NL"
}Structured components
{
"components": {
"street": "Prinsengracht",
"houseNumber": "263",
"postcode": "1016 GV",
"city": "Amsterdam",
"country": "NL"
}
}With provider trail
{
"query": "Prinsengracht 263, Amsterdam",
"country": "NL",
"debug": true
}Responses
| Status | Body | Description |
|---|---|---|
200 |
ValidateResponse | Validation result. candidates and trace are present only when the request set "debug": true. |
400 |
ValidationError | The request body failed schema validation. issues lists each violation. |
401 |
Error | Missing, malformed, unknown, or revoked API key. |
429 |
Error | The account's monthly plan credits plus purchased extra credits are used up. Resolve by upgrading the plan or buying a credit pack in the console; the counter resets on the 1st of each month (UTC). |
200 example
Typo fixed by the engine
{
"verdict": "correctable",
"confidence": 84,
"address": {
"street": "Prinsengracht",
"houseNumber": "263",
"postcode": "1016 GV",
"city": "Amsterdam",
"country": "NL",
"lat": 52.3752,
"lng": 4.8836
},
"components": {
"street": "corrected",
"houseNumber": "match",
"city": "corrected",
"country": "match"
},
"provider": "bag",
"reasons": [
"components_corrected"
],
"cached": false
}400 example
{
"message": "Validation failed",
"issues": [
{
"code": "too_small",
"minimum": 1,
"path": [
"query"
],
"message": "Too small, expected string to have >=1 characters"
}
]
}401 example
missing
{
"message": "Missing API key. Send it as: Authorization: Bearer ak_live_…"
}malformed
{
"message": "Malformed API key"
}unknown
{
"message": "Unknown or revoked API key"
}429 example
{
"message": "Monthly credits exhausted (500 plan credits on the free plan). Upgrade or buy credits at https://geoverdict.com/dashboard"
}POST/v1/autocomplete
Autocomplete an address
Returns up to limit (default 5) address suggestions for a partial query. Suggestions are deduplicated across providers.
Each uncached call costs 1 credit per provider that answered. A cache hit or a request where no provider answers costs 0. sessionToken is echoed back for client-side correlation only; it does not deduplicate billing.
For browser-side autocomplete use the embeddable widget with a publishable token (/v1/widget/autocomplete) instead of exposing a secret key.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
source |
query | string length 0–64 |
Fallback for the source body field; the body value wins when both are set. |
Request body
| Field | Type | Description |
|---|---|---|
query required |
string length 1–200 |
Partial address text. |
country |
string length 2–2 |
ISO 3166-1 alpha-2 country bias/filter. |
language |
string length 2–8 |
|
limit |
integer range 1–10, default 5 |
Maximum number of suggestions. |
sessionToken |
string length 0–64 |
Client-generated correlation id, echoed back unchanged. Correlation only; it does not deduplicate billing. |
source |
string length 1–64 |
Usage segmentation tag, as on /v1/validate. |
{
"query": "Prinsengracht 26",
"country": "NL",
"limit": 5
}
Responses
| Status | Body | Description |
|---|---|---|
200 |
AutocompleteResponse | Suggestions ordered by provider result order, deduplicated by label. |
400 |
ValidationError | The request body failed schema validation. issues lists each violation. |
401 |
Error | Missing, malformed, unknown, or revoked API key. |
429 |
Error | The account's monthly plan credits plus purchased extra credits are used up. Resolve by upgrading the plan or buying a credit pack in the console; the counter resets on the 1st of each month (UTC). |
200 example
{
"suggestions": [
{
"label": "Prinsengracht 263, 1016GV Amsterdam",
"components": {
"street": "Prinsengracht",
"houseNumber": "263",
"postcode": "1016 GV",
"city": "Amsterdam",
"country": "NL"
},
"location": {
"lat": 52.3752,
"lng": 4.8836
},
"provider": "bag"
}
],
"sessionToken": null,
"cached": false
}400 example
{
"message": "Validation failed",
"issues": [
{
"code": "too_small",
"minimum": 1,
"path": [
"query"
],
"message": "Too small, expected string to have >=1 characters"
}
]
}401 example
missing
{
"message": "Missing API key. Send it as: Authorization: Bearer ak_live_…"
}malformed
{
"message": "Malformed API key"
}unknown
{
"message": "Unknown or revoked API key"
}429 example
{
"message": "Monthly credits exhausted (500 plan credits on the free plan). Upgrade or buy credits at https://geoverdict.com/dashboard"
}POST/v1/widget/autocomplete
Browser widget autocomplete
Browser-safe autocomplete for the embeddable GeoVerdict widget. Authenticates with a project-scoped publishable token (gv_pk_live_... / gv_pk_test_...) and requires a request Origin on the token's exact HTTPS origin allowlist. Secret API keys (ak_...) and console sessions are rejected.
Suggestions are mapped to a browser-safe shape with opaque ids; provider-native identifiers and raw metadata are never returned. Per-token and per-origin rate limits of 120 requests/minute apply in addition to the account credit gate. Billing matches server autocomplete: 1 credit per answering provider per uncached call.
Most integrations should not call this endpoint directly; the hosted widget (https://geoverdict.com/widget/v1/geoverdict-autocomplete.js) implements the contract, keyboard navigation, and accessibility.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
Origin required |
header | string | Exact HTTPS origin of the embedding page. Must be on the publishable token's origin allowlist. |
Request body
| Field | Type | Description |
|---|---|---|
query required |
string length 1–200 |
Partial address text. |
country |
string length 2–2 |
ISO 3166-1 alpha-2 country bias/filter. |
language |
string length 2–8 |
|
limit |
integer range 1–10, default 5 |
Maximum number of suggestions. |
sessionToken |
string length 0–64 |
Client-generated correlation id, echoed back unchanged. Correlation only; it does not deduplicate billing. |
source |
string length 1–64 |
Usage segmentation tag, as on /v1/validate. |
{
"query": "Prinsengracht 26",
"country": "NL",
"limit": 5,
"sessionToken": "3f5a1c9e-session",
"source": "widget"
}
Responses
| Status | Body | Description |
|---|---|---|
200 |
WidgetAutocompleteResponse | Browser-safe suggestions. Access-Control-Allow-Origin echoes the validated origin. |
400 |
ValidationError | The request body failed schema validation. issues lists each violation. |
401 |
Error | Missing, malformed, revoked, or origin-restricted publishable token. |
403 |
Error | The request carried no Origin header or the origin is not an exact HTTPS origin. |
429 |
Error | Widget rate limit reached (120 requests/minute per live token and per origin; 60/minute for test tokens) or the applicable live/test credit allowance is exhausted. |
200 example
{
"suggestions": [
{
"id": "gv_s_1f7c0a92be34d56e78a90c12",
"label": "Prinsengracht 263, 1016GV Amsterdam",
"provider": "bag",
"address": {
"street": "Prinsengracht",
"houseNumber": "263",
"postcode": "1016 GV",
"city": "Amsterdam",
"country": "NL"
}
}
],
"sessionToken": "3f5a1c9e-session",
"cached": false
}400 example
{
"message": "Validation failed",
"issues": [
{
"code": "too_small",
"minimum": 1,
"path": [
"query"
],
"message": "Too small, expected string to have >=1 characters"
}
]
}401 example
{
"message": "Unknown, revoked, or origin-restricted publishable token"
}403 example
{
"message": "A permitted HTTPS Origin is required"
}429 example
rateLimit
{
"message": "Widget autocomplete rate limit reached"
}credits
{
"message": "Monthly project credits exhausted"
}/v1/widget/autocomplete
Widget CORS preflight
CORS preflight for the widget endpoint. Because browsers omit the Authorization value during preflight, the server reflects an origin only when an active publishable token currently allows it; the subsequent POST still binds the exact token and origin.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
Origin required |
header | string | |
Access-Control-Request-Method required |
header | POST |
Responses
| Status | Body | Description |
|---|---|---|
204 |
no body | Origin currently allowed by at least one active token; CORS headers set. |
403 |
no body | Origin missing, not HTTPS, or not on any active token's allowlist. |
GET/v1/usage
Usage and credit balance
Current-month credit consumption plus recent activity for the account that owns the API key. This call consumes no credits and never writes a usage event, but the shared API-key quota gate still returns 429 after the account exhausts its credits. Displays can lag the ledger by up to 60 seconds.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
source |
query | string | Filter summary, daily, and recent to a single source tag. sources always covers all tags. |
Responses
| Status | Body | Description |
|---|---|---|
200 |
UsageResponse | Usage report. |
401 |
Error | Missing, malformed, unknown, or revoked API key. |
429 |
Error | The account's monthly plan credits plus purchased extra credits are used up. Resolve by upgrading the plan or buying a credit pack in the console; the counter resets on the 1st of each month (UTC). |
200 example
{
"plan": "free",
"quota": 500,
"extraCredits": 0,
"creditsUsedThisMonth": 42,
"filteredBySource": null,
"daily": [
{
"day": "2026-08-02",
"n": 12,
"credits": 10
}
],
"summary": [
{
"endpoint": "validate",
"verdict": "valid",
"n": 30,
"credits": 28
}
],
"sources": [
{
"source": "checkout",
"n": 25,
"credits": 22
},
{
"source": "",
"n": 17,
"credits": 20
}
],
"recent": [
{
"endpoint": "validate",
"verdict": "valid",
"provider": "bag",
"latency_ms": 132,
"cached": 0,
"credits": 1,
"query": "Prinsengracht 263, Amsterdam",
"source": "checkout",
"at": "2026-08-02 14:03:11"
}
]
}401 example
missing
{
"message": "Missing API key. Send it as: Authorization: Bearer ak_live_…"
}malformed
{
"message": "Malformed API key"
}unknown
{
"message": "Unknown or revoked API key"
}429 example
{
"message": "Monthly credits exhausted (500 plan credits on the free plan). Upgrade or buy credits at https://geoverdict.com/dashboard"
}POST/demo/validate
Homepage demo validation
Unauthenticated validation used by the geoverdict.com homepage demo. Forced to the Netherlands (country: NL), costs 0 credits, and is rate limited per visitor IP over rolling windows: 10 requests / 5 minutes, 20 / 24 hours, 50 / 7 days, and 200 / 30 days. Not intended for production integrations; use /v1/validate with an API key instead.
Request body
| Field | Type | Description |
|---|---|---|
query required |
string length 1–200 |
{
"query": "prinsengrach 263, amsterdm"
}
Responses
| Status | Body | Description |
|---|---|---|
200 |
DemoValidateResponse | Validation result (same core fields as /v1/validate, without cached). |
400 |
Error | Missing or invalid query. |
429 |
DemoRateLimitError | One or more rolling demo allowances are exhausted. The Retry-After header carries the longest required wait in seconds. |
400 example
{
"message": "Type an address first"
}429 example
{
"message": "Demo request allowance reached.",
"limits": [
{
"window": "five_minute",
"max": 10,
"periodSeconds": 300,
"retryAfterSeconds": 233
}
],
"retryAfterSeconds": 233,
"freeMonthlyCredits": 500
}Schemas
Error
| Field | Type | Description |
|---|---|---|
message required |
string |
ValidationError
| Field | Type | Description |
|---|---|---|
message required |
Validation failed |
|
issues required |
array of object | Machine-readable list of schema violations (Zod issue objects). |
AddressComponents
Structured address parts. All fields optional.
| Field | Type | Description |
|---|---|---|
street |
string length 1–200 |
|
houseNumber |
string length 1–20 |
House number including any suffix, e.g. 263, 2B, 2/B. |
postcode |
string length 1–20 |
|
city |
string length 1–100 |
|
state |
string length 1–100 |
|
country |
string length 2–2 |
ISO 3166-1 alpha-2 code, e.g. NL. |
ValidateRequest
Provide query, components, or both. At least one is required.
| Field | Type | Description |
|---|---|---|
query |
string length 1–500 |
Free-form address text. |
components |
AddressComponents | Structured address parts. All fields optional. |
country |
string length 2–2 |
ISO 3166-1 alpha-2 country bias/filter. Providers that do not cover this country are skipped. |
language |
string length 2–8 |
Preferred response language (e.g. en, nl-NL). |
source |
string length 1–64 |
Free-form tag for segmenting usage per feature or system (e.g. checkout). Appears in /v1/usage and the console. |
debug |
boolean | When true, the response additionally includes candidates (all scored provider results) and trace (the provider routing trail). |
Verdict
- valid: confidence at or above the account's valid threshold and every checked component matched.
- correctable: the address was found but something was fixed (the response carries the standardized address).
- invalid: below the correctable threshold, or a supplied house number could not be matched.
One of: valid, correctable, invalid
Reason
One of: no_results, low_confidence, no_house_number_match, components_incomplete, components_corrected
ComponentVerdict
One of: match, corrected, mismatch, missing, not-checked
ComponentVerdicts
Per-component comparison of the best result against the input. Keys are only present for checked components.
| Field | Type | Description |
|---|---|---|
street |
ComponentVerdict | |
houseNumber |
ComponentVerdict | |
postcode |
ComponentVerdict | |
city |
ComponentVerdict | |
country |
ComponentVerdict |
ResolvedAddress
The standardized address of the best result, or null when no provider returned anything.
| Field | Type | Description |
|---|---|---|
street |
string | |
houseNumber |
string | |
postcode |
string | |
city |
string | |
state |
string | |
country |
string | ISO 3166-1 alpha-2. |
lat |
number | |
lng |
number |
GeoPoint
| Field | Type | Description |
|---|---|---|
lat required |
number | |
lng required |
number |
TraceStep
One provider attempt in the routing trail.
| Field | Type | Description |
|---|---|---|
provider required |
string | |
status required |
ok · error · timeout · aborted · skipped · circuit-open · no-coverage · unsupported · unknown-provider |
|
latencyMs |
integer | |
resultCount |
integer | |
error |
string |
Candidate
A scored provider result (debug only).
| Field | Type | Description |
|---|---|---|
provider required |
string | |
components required |
AddressComponents | Structured address parts. All fields optional. |
location |
GeoPoint | |
providerConfidence |
number range 0–1 |
Provider-native confidence normalized to 0..1; absent when the provider reports none. |
label |
string | |
raw |
any | Provider-native payload; shape varies per provider. |
confidence required |
integer range 0–100 |
|
componentVerdicts required |
ComponentVerdicts | Per-component comparison of the best result against the input. Keys are only present for checked components. |
ValidateResponse
| Field | Type | Description |
|---|---|---|
verdict required |
Verdict | - valid: confidence at or above the account's valid threshold and every checked component matched.
- correctable: the address was found but something was fixed (the response carries the standardized address).
- invalid: below the correctable threshold, or a supplied house number could not be matched.
|
confidence required |
integer range 0–100 |
Unified confidence score, comparable across providers. 0 when no result was found. |
address required |
ResolvedAddress | The standardized address of the best result, or null when no provider returned anything. |
components required |
ComponentVerdicts | Per-component comparison of the best result against the input. Keys are only present for checked components. |
provider required |
string or null | Id of the provider that produced the best result, or null when there was none. |
reasons required |
array of Reason | |
cached required |
boolean | True when served from the response cache. Cached responses cost 0 credits. |
candidates |
array of Candidate | Only present when the request set "debug": true. |
trace |
array of TraceStep | Only present when the request set "debug": true. |
AutocompleteRequest
| Field | Type | Description |
|---|---|---|
query required |
string length 1–200 |
Partial address text. |
country |
string length 2–2 |
ISO 3166-1 alpha-2 country bias/filter. |
language |
string length 2–8 |
|
limit |
integer range 1–10, default 5 |
Maximum number of suggestions. |
sessionToken |
string length 0–64 |
Client-generated correlation id, echoed back unchanged. Correlation only; it does not deduplicate billing. |
source |
string length 1–64 |
Usage segmentation tag, as on /v1/validate. |
Suggestion
| Field | Type | Description |
|---|---|---|
label required |
string | Formatted display label. |
components required |
AddressComponents | Structured address parts. All fields optional. |
location |
GeoPoint | |
provider required |
string |
AutocompleteResponse
| Field | Type | Description |
|---|---|---|
suggestions required |
array of Suggestion | |
sessionToken required |
string or null | The request's sessionToken, echoed back; null when none was sent. |
cached required |
boolean |
WidgetSuggestion
| Field | Type | Description |
|---|---|---|
id required |
string | Opaque suggestion id scoped to the publishable token (prefix gv_s_). |
label required |
string | |
provider required |
string | |
address required |
AddressComponents | Structured address parts. All fields optional. |
WidgetAutocompleteResponse
| Field | Type | Description |
|---|---|---|
suggestions required |
array of WidgetSuggestion | |
sessionToken required |
string or null | |
cached required |
boolean |
UsageResponse
| Field | Type | Description |
|---|---|---|
plan required |
string | Current plan id (free, starter, growth, scale). |
quota required |
integer | Monthly plan credits. |
extraCredits required |
integer | Purchased pack credits still attributed to the account. Consumed only after the monthly grant. |
creditsUsedThisMonth required |
integer | Credits consumed this calendar month (UTC). May lag the ledger by up to 60 seconds. |
filteredBySource required |
string or null | The source filter applied to summary, daily, and recent, or null. |
daily required |
array of object | Daily request and credit counts for the last 14 days. |
summary required |
array of object | Current-month totals grouped by endpoint and verdict. |
sources required |
array of object | Current-month totals per source tag (untagged traffic groups under an empty string). Always account-wide, ignoring the source filter. |
recent required |
array of object | The 50 most recent usage events. |
DemoValidateResponse
| Field | Type | Description |
|---|---|---|
verdict required |
Verdict | - valid: confidence at or above the account's valid threshold and every checked component matched.
- correctable: the address was found but something was fixed (the response carries the standardized address).
- invalid: below the correctable threshold, or a supplied house number could not be matched.
|
confidence required |
integer range 0–100 |
|
address required |
ResolvedAddress | The standardized address of the best result, or null when no provider returned anything. |
components required |
ComponentVerdicts | Per-component comparison of the best result against the input. Keys are only present for checked components. |
provider required |
string or null | |
reasons required |
array of Reason |
DemoRateLimitError
| Field | Type | Description |
|---|---|---|
message required |
string | |
limits required |
array of object | Every rolling window that is currently exhausted. |
retryAfterSeconds required |
integer | The longest wait among all exhausted windows. |
freeMonthlyCredits required |
integer | Monthly credits included with a free account (sign-up removes the demo limits). |