API reference
One call answers one question: for this address, what should happen. The answer always carries a reason code, because a decision you cannot explain is one you cannot act on.
Authenticating
Every request carries a key as a bearer token. Keys are created in Settings, shown once, and stored as a hash, so a lost key is replaced rather than recovered.
Keys begin ml_test_ or ml_live_, and carry scopes. A resolve needs the resolve scope and nothing more. Give an integration the narrowest set that does its job.
A test key is the same engine with the meters off: it resolves against your published configuration through the same code, is never billed or counted against your plan, and rate limits separately, so developing cannot starve production. Build againstml_test_, swap the key to go live.
Deciding an address
Send an address or a coordinate. An address is geocoded first. A coordinate is used as given, which is what makes an answer reproducible.
Requestbash
curl https://api.maplogics.com/v1/resolve \
-H "Authorization: Bearer ml_live_..." \
-H "Content-Type: application/json" \
-d '{
"address": {
"line1": "2 E Main St",
"city": "Richmond",
"region": "VA",
"postal_code": "23219",
"country": "US"
},
"context": { "service": "hvac" }
}'Responsejson
{
"request_id": "req_9039f987",
"status": "resolved",
"reason_code": "ASSIGNMENT_RULE_MATCHED",
"serviceable": true,
"territory": { "id": "trr_...", "name": "Richmond Central" },
"location": { "id": "loc_...", "name": "Richmond Central" },
"outputs": {},
"ruleset_version": "cfg_01M0X5..."
}Unserviceable is a 200. The question was answered and the answer was no. A non-2xx means we could not answer, which is a different thing and usually your retry.
Try it from here
Paste a test key and send a real request to the API from this page. Test keys work from the documentation without any origin setup, resolve against your published configuration, and are never billed or metered. A live key is refused from a browser unless its origin is allow-listed, which is the point of live keys.
The key stays in this page's memory and goes only to the API, in the same header your code will send. Nothing here stores it.
From an AI agent
The same decisions are offered as tools over the Model Context Protocol athttps://api.maplogics.com/mcp, with the same key in the same header. Claude and other MCP clients can check an address, check a coordinate and explain a decision. TheMCP guide has the setup for each client.
Reason codes
Branch on reason_code, never on message. The message is written for a person and will be reworded; the code is the contract.
resolved
PRIMARY_ELIGIBLE_TERRITORY- Resolved to the highest-priority eligible territory.
ASSIGNMENT_RULE_MATCHED- An assignment rule selected this territory explicitly.
FALLBACK_ASSIGNED- No primary candidate could take it, so the configured fallback did.
unserviceable
ALL_CANDIDATES_INELIGIBLE- A territory covers this address, but no assigned location can perform the requested service. Check the capabilities on those locations.
EXCLUDED_BY_RULE- Every candidate was removed by an exclusion rule.
no_coverage
NO_TERRITORY_COVERS_POINT- No territory covers this address. Extend a boundary or add one to cover it.
ambiguous
EQUAL_PRIORITY_OVERLAP- Two or more territories are equally eligible and nothing breaks the tie. Set an explicit priority, or narrow one boundary.
invalid_input
ADDRESS_NOT_FOUND- No geocoder could place this address. Check it for typos, or send a coordinate instead.
ADDRESS_TOO_IMPRECISE- This address could only be placed to a postal code or region, which is not precise enough to route: a boundary can run through it. Add a street number, or send a coordinate.
INVALID_INPUT- The request could not be understood.
Never collapse these into a boolean. "No territory covers this address", "a territory covers it but nobody can do the work", and "two territories tie" have three different remedies. A single false throws away the one thing that tells you which.
Errors
Every error carries a stable code, the same request_id as the response header, and a message for a person.
| Code | HTTP | Meaning |
|---|---|---|
invalid_request | 400 | The request body could not be understood. |
invalid_coordinate | 400 | lat must be between -90 and 90 and lng between -180 and 180. Note the order: [lng, lat]. |
missing_location_input | 400 | Provide either an address or a coordinate. |
unauthorized | 401 | Provide an API key as "Authorization: Bearer ml_live_...". |
invalid_api_key | 401 | That API key is not valid. Keys are shown once at creation and cannot be recovered. |
revoked_api_key | 401 | That API key has been revoked. |
invalid_token | 401 | That access token is not valid or has expired. Connect again from your AI client to sign in. |
organization_suspended | 403 | This organization is not active. Contact support to restore access. |
forbidden | 403 | Your role in this organization does not allow this action. |
forbidden_origin | 403 | This key is not allowed to be called from that origin. |
insufficient_scope | 403 | That key does not carry the scope this endpoint needs. |
no_published_configuration | 409 | This organisation has not published a configuration yet, so there is nothing to resolve against. |
conflict | 409 | Someone else changed this since you loaded it. Reload to see their version. |
precondition_required | 428 | This change would overwrite an existing record. Send the version you are editing so we can check nobody else changed it first. |
idempotency_conflict | 409 | That idempotency key was already used with a different request body. |
rate_limited | 429 | Too many requests. Retry after the interval in the Retry-After header. |
quota_exceeded | 429 | This organization has used the decisions its plan includes this month. The count resets at midnight UTC on the first of next month. Plan limits are at https://app.maplogics.com/billing. |
geocoder_unavailable | 502 | The address could not be geocoded because every provider failed. Retry, or send a coordinate. |
runtime_unavailable | 503 | The configuration for this organisation could not be loaded. This is on our side. |
not_found | 404 | No record with that identifier exists for this organization. |
internal_error | 500 | Something went wrong on our end. Quote the request id if you get in touch. |
Webhooks
Rather than polling for changes, subscribe. We send a signed POST when a configuration goes live, when one is rolled back, and when a location is archived.
Each delivery carries X-MapLogics-Signature: t=<unix>,v1=<hex>. Verify it before trusting the body: your endpoint is public, and without the check anyone who learns the URL can tell you your routing changed.
Verifying a deliveryjavascript
import crypto from 'node:crypto'
// The timestamp is inside the signed material, so it cannot be
// edited to make an old body look current.
export function verify(secret, rawBody, header) {
const parts = Object.fromEntries(
header.split(',').map((p) => p.trim().split('=')),
)
const age = Math.abs(Date.now() / 1000 - Number(parts.t))
if (age > 300) return false
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex')
// Constant time: comparing with === leaks where the first
// difference is, which over many attempts is enough.
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1),
)
}Answer 2xx once you have stored it. Anything else is retried with widening backoff over about two hours, and every attempt is visible in Settings with the status we got back.