> ## Documentation Index
> Fetch the complete documentation index at: https://withforerunner.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Understand and resolve API errors

## 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:

```json theme={null}
{
  "errors": [
    {
      "message": "Address, parcelId, or coordinates must be provided",
      "field": "file"
    }
  ],
  "file": null
}
```

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:

```json theme={null}
{
  "errors": [
    {
      "field": "geometry",
      "message": "Only one of geometry or parcelId can be provided."
    }
  ],
  "properties": []
}
```

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

| Code | When |
| - | - |
| `200` | The request succeeded. `errors` is empty. |
| `400` | The request failed validation. `errors` names each field at fault. |
| `401` | The API key is missing, malformed, or invalid. |
| `403` | Blocked by our web application firewall. See below. |
| `404` | Updating or deleting a record whose id is not in your account. |
| `409` | Another resource already uses that `externalSystemId`. |
| `429` | Too many requests from your IP. Back off and retry. |
| `500` | Unexpected server error. Retry with backoff. |

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`.

<Note>
  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.
</Note>

## Request blocked by the firewall (403)

<Warning>
  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.
</Warning>

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:"`

```bash theme={null}
curl https://app.withforerunner.com/api/v1/properties \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -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

<AccordionGroup>
  <Accordion title="Invalid UUID format for id parameter">
    A path `id` is not a UUID. Check for a truncated or URL-encoded value. This
    body carries `errors` alone, with no resource field.
  </Accordion>

  <Accordion title="Document upload already exists with external system id ...">
    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.
  </Accordion>

  <Accordion title="Log not found for this account">
    A `404` from updating or deleting a `Log`. The id either does not exist or
    belongs to another account.
  </Accordion>

  <Accordion title="Improvement not found">
    The SI/SD equivalent, returned as a `400` rather than a `404`.
  </Accordion>

  <Accordion title="Address, parcelId, or coordinates must be provided">
    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.
  </Accordion>

  <Accordion title="MIME type ... is not allowed for document type ...">
    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](/docs/files/file-types), or upload under a document type that accepts this format.
  </Accordion>

  <Accordion title="File ... exceeds the maximum size of ... MB">
    Limits are per media type and are listed on Submit File. Compress the file, or split a large document, before retrying.
  </Accordion>
</AccordionGroup>

## 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.