Skip to main content

Error response format

Every response the API itself produces carries an errors array. It is empty when the request succeeded, and otherwise holds one entry per problem, alongside the endpoint’s resource field set to null or an empty array:
Both keys are always present on an error entry. message is human-readable, and field is the request field at fault, as a dot-separated path (data.type, property.externalSystemId). A single response can report several problems at once, so read the whole array rather than the first entry:
Two responses do not follow that shape, because they never reach the API: a 401 is plain text, and a firewall 403 or 429 is HTML.

Status codes

Each endpoint documents the codes it returns, with the body shape for each. A successful create returns 200, not 201, and a delete returns 200 with a body rather than 204.
A missing id does not always produce a 404. SI/SD answers one with 400 and Improvement not found, and a malformed id is a 400 everywhere.

Request blocked by the firewall (403)

Our web application firewall blocks requests whose User-Agent contains PostmanRuntime, python-requests, or HeadlessChrome, to deter scraping. You get a 403 even with a valid API key. This is the single most common surprise when trying the API from a desktop client.
Clear or replace the User-Agent header before sending:
  • Postman - Headers, then delete User-Agent or set it to an empty value
  • Python - requests sends python-requests/x.y.z; set your own User-Agent
  • cURL - not blocked; if you need to, pass -H "User-Agent:"

Rate limits (429)

The API itself does not throttle by API key. Our CDN caps requests per source IP address over a rolling five-minute window and answers anything over the cap with a 429 and an HTML body. The ceiling is high enough that an integration paging through data sequentially will not reach it, but the bucket is shared by everything leaving your network, including staff using the Forerunner app. Spread bulk work out rather than firing it in parallel, and back off when you see a 429.

Common validation errors

A path id is not a UUID. Check for a truncated or URL-encoded value. This body carries errors alone, with no resource field.
A file already uses that externalSystemId, so the upload returned 409 and nothing was created. Look the file up with GET /api/v1/files?externalSystemId=... before retrying.
A 404 from updating or deleting a Log. The id either does not exist or belongs to another account.
The SI/SD equivalent, returned as a 400 rather than a 404.
A file or SI/SD record arrived with no way to locate a property. Related failures name what went wrong more precisely: Parcel with id ... not found, Provided point is outside of the account boundary., and Could not determine address from the given data. Send as many of address, parcel ID, and coordinates as you have — together they match far more reliably than any one alone.
Each document type accepts its own set of media types, and the file’s media type is not one of them. Check the document type’s allowed types under File types, or upload under a document type that accepts this format.
Limits are per media type and are listed on Submit File. Compress the file, or split a large document, before retrying.

Retrying

Retry 429, 500, 502, 503, and 504 with exponential backoff. The client errors this API returns — 400, 401, 403, 404, and 409 — are deterministic: the same request fails the same way until you change it, so retrying only adds load. A 409 in particular can never succeed on retry, because the resource it conflicts with already exists.

Getting help

Every response from the API carries an x-request-id header. Include it when you contact support: it identifies the exact request in our logs and is far faster to chase than a timestamp.