Errors
Every failure is an HTTP status with a JSON body carrying a human-readable detail. The single endpoint fails the whole request; batch isolates failures to the row.
Status codes
A failed request to /v1/underwrite returns a non-2xx status and a body of the form { "detail": "…" }:
| Status | Meaning | What to do |
|---|---|---|
401 | Invalid or missing API key. The X-API-Key header was absent or not a live key. | Check the header and that the key hasn’t been revoked. See Authentication. |
422 | Unprocessable request. The address couldn’t be geocoded, or a named ruleset_version doesn’t exist. Also the body failing field validation. | Fix the address or the field, then retry. This is a request problem, not a transient one. |
429 | Rate limit exceeded. You passed your tier’s per-second request ceiling. | Back off and retry. Move volume through batch; see Plans & limits. |
503 | Service unavailable. A required upstream or configuration was missing on our side. | Retry with backoff. If it persists, it’s ours to fix — contact support. |
A validation or geocode failure on the single endpoint looks like this:
{
"detail": "No match for address: 123 Nowhere Rd"
}Batch fails per row, not per request
Bulk underwrite is different by design. A malformed or unlocatable address doesn’t fail the call — the request still returns 200 OK, and the failure is captured on that one results entry with a typed error:
{
"ok": false,
"address": "123 Nowhere Rd",
"error": { "type": "geocode_failed", "detail": "No match for address" }
}The error.type is machine-readable — geocode_failed for an address that couldn’t be located, unknown_ruleset for a bad ruleset_version — so you can branch on it while flattening results. Auth and rate-limit failures still surface as 401 and 429 on the request itself, since they concern the whole call.

