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

## Overview

The Forerunner API uses conventional HTTP status codes to indicate the success or failure of requests. This guide helps you understand error responses and resolve common issues.

***

## HTTP Status Codes

### Success Codes

| Code    | Meaning    | Description                             |
| ------- | ---------- | --------------------------------------- |
| **200** | OK         | Request succeeded                       |
| **201** | Created    | Resource created successfully           |
| **204** | No Content | Request succeeded with no response body |

### Client Error Codes

| Code    | Meaning              | Description                                            |
| ------- | -------------------- | ------------------------------------------------------ |
| **400** | Bad Request          | Invalid request syntax or parameters                   |
| **401** | Unauthorized         | Missing or invalid API key                             |
| **403** | Forbidden            | Request blocked by WAF or insufficient permissions     |
| **404** | Not Found            | Requested resource does not exist                      |
| **409** | Conflict             | Resource already exists (duplicate `externalSystemId`) |
| **422** | Unprocessable Entity | Request syntax is valid but contains semantic errors   |
| **429** | Too Many Requests    | Rate limit exceeded                                    |

### Server Error Codes

| Code    | Meaning               | Description                                |
| ------- | --------------------- | ------------------------------------------ |
| **500** | Internal Server Error | Unexpected server error occurred           |
| **502** | Bad Gateway           | Temporary server issue, retry with backoff |
| **503** | Service Unavailable   | Server is temporarily unavailable          |
| **504** | Gateway Timeout       | Request timed out, retry with backoff      |

***

## Error Response Format

All error responses follow a consistent JSON structure with an `errors` array:

```json theme={null}
{
  "errors": [
    {
      "message": "Human-readable error description",
      "field": "fieldName or path.to.field"
    }
  ],
  "file": null
}
```

<Note>
  Error responses also include endpoint-specific fields (like `file`, `sisd`, `property`, `properties`) set to `null` or empty arrays.
</Note>

### Example Error Responses

<CodeGroup>
  ```json Invalid File Type theme={null}
  {
    "errors": [
      {
        "field": "fileType",
        "message": "Could not find AccountDocumentType with name invalidType for account abc123. Must be one of elevation_certificate, flood_map"
      }
    ],
    "file": null
  }
  ```

  ```json Missing Required Field theme={null}
  {
    "errors": [
      {
        "field": "value",
        "message": "Required field 'value' is missing"
      }
    ],
    "sisd": null,
    "property": null
  }
  ```

  ```json Invalid Date Format theme={null}
  {
    "errors": [
      {
        "field": "date",
        "message": "Date must be in MM/DD/YYYY format"
      }
    ],
    "sisd": null,
    "property": null
  }
  ```

  ```json Multiple Validation Errors theme={null}
  {
    "errors": [
      {
        "field": "geometry",
        "message": "Only one of geometry or parcelId can be provided."
      },
      {
        "field": "page",
        "message": "Page must be at least 1"
      }
    ],
    "properties": []
  }
  ```

  ```json Duplicate Resource (409 Conflict) theme={null}
  {
    "errors": [
      {
        "field": "externalSystemId",
        "message": "Document upload already exists with external system id 12345"
      }
    ],
    "file": null
  }
  ```

  ```json Invalid UUID theme={null}
  {
    "errors": [
      {
        "field": "id",
        "message": "Invalid UUID format for id parameter"
      }
    ]
  }
  ```
</CodeGroup>

***

## Common Errors

### Authentication Errors (401 Unauthorized)

<AccordionGroup>
  <Accordion title="Invalid or missing API key">
    **Cause:** API key is malformed, missing, or invalid

    **Solution:**

    * Verify API key is correctly formatted
    * Ensure `Bearer` prefix is included in Authorization header
    * Check for extra whitespace in API key value
    * Add Authorization header to all requests
    * Contact your Customer Success Manager for a new API key if expired

    ```javascript theme={null}
    // Correct format
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY'
    }
    ```

    ```bash theme={null}
    # Example with cURL
    curl https://app.withforerunner.com/api/v1/properties \
      -H "Authorization: Bearer YOUR_API_KEY"
    ```
  </Accordion>
</AccordionGroup>

### Request Blocked by WAF (403 Forbidden)

<AccordionGroup>
  <Accordion title="Request blocked when using Postman, Insomnia, or other HTTP clients">
    **Cause:** Forerunner's web application firewall (WAF) blocks requests with common HTTP client `User-Agent` headers to prevent automated web scraping. This returns a **403 Forbidden** response even when your API key is valid.

    **Solution:**

    * Remove or clear the `User-Agent` header in your HTTP client before sending requests
    * In **Postman**: Go to Headers, find `User-Agent`, and either delete it or set it to an empty value
    * In **Insomnia**: Go to Headers and remove the `User-Agent` entry
    * **cURL** does not typically trigger this issue, but if it does, pass an empty User-Agent:

    ```bash theme={null}
    curl https://app.withforerunner.com/api/v1/properties \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "User-Agent:"
    ```
  </Accordion>
</AccordionGroup>

### Validation Errors

<AccordionGroup>
  <Accordion title="Missing required fields">
    **Cause:** Required parameter not provided in request

    **Error example:**

    ```json theme={null}
    {
      "errors": [
        {
          "field": "value",
          "message": "Required field 'value' is missing"
        }
      ]
    }
    ```

    **Solution:**

    * Check API documentation for required fields
    * Ensure all required fields are included in request body

    ```javascript theme={null}
    // Missing required field
    {
      "type": "damage"
      // Missing: value, date
    }

    // Correct
    {
      "type": "damage",
      "value": 125000,
      "date": "03/15/2024"
    }
    ```
  </Accordion>

  <Accordion title="Invalid date format">
    **Cause:** Date provided in incorrect format

    **Error example:**

    ```json theme={null}
    {
      "errors": [
        {
          "field": "date",
          "message": "Date must be in MM/DD/YYYY format"
        }
      ]
    }
    ```

    **Solution:**

    * Use `MM/DD/YYYY` format for date fields
    * Ensure leading zeros for single-digit months/days

    ```javascript theme={null}
    // Incorrect
    { "date": "2024-03-15" }

    // Correct
    { "date": "03/15/2024" }
    ```
  </Accordion>

  <Accordion title="Invalid file type">
    **Cause:** Unsupported or incorrect file type specified

    **Error example:**

    ```json theme={null}
    {
      "errors": [
        {
          "field": "fileType",
          "message": "Could not find AccountDocumentType with name invalidType"
        }
      ]
    }
    ```

    **Solution:**

    * Use valid file types provided by your account configuration
    * Check for typos in `fileType` parameter
    * Common types include: `elevation_certificate`, `permit`, `map`, `photo`, `letter`

    ```javascript theme={null}
    // Incorrect
    { "fileType": "elevation-certificate" }

    // Correct
    { "fileType": "elevation_certificate" }
    ```
  </Accordion>

  <Accordion title="Invalid GeoJSON">
    **Cause:** Malformed GeoJSON coordinates or geometry

    **Solution:**

    * Ensure GeoJSON follows standard format
    * Verify longitude comes before latitude: `[longitude, latitude]`
    * Validate GeoJSON structure with type and coordinates

    ```javascript theme={null}
    // Incorrect
    {
      "coordinates": {
        "lat": 39.78,
        "lng": -89.65
      }
    }

    // Correct
    {
      "type": "Point",
      "coordinates": [-89.65, 39.78]
    }
    ```
  </Accordion>

  <Accordion title="Invalid UUID format">
    **Cause:** Resource ID is not a valid UUID

    **Error example:**

    ```json theme={null}
    {
      "errors": [
        {
          "field": "id",
          "message": "Invalid UUID format for id parameter"
        }
      ]
    }
    ```

    **Solution:**

    * Ensure resource IDs are valid UUIDs
    * Verify you're using the correct ID from previous API responses
  </Accordion>
</AccordionGroup>

### Resource Errors

<AccordionGroup>
  <Accordion title="Resource not found">
    **Cause:** Requested resource does not exist

    **Solution:**

    * Verify resource ID is correct
    * Ensure resource hasn't been deleted
    * Check you're querying the correct environment (staging vs production)
  </Accordion>

  <Accordion title="Property not found or cannot be matched">
    **Cause:** Could not match address or parcel to a property

    **Error example:**

    ```json theme={null}
    {
      "errors": [
        {
          "field": "address",
          "message": "Could not find property matching the provided address"
        }
      ]
    }
    ```

    **Solution:**

    * Verify address format and spelling
    * Ensure property exists in your organization's database
    * Try using parcel ID or coordinates if address matching fails
    * Provide multiple identifiers for better matching

    ```javascript theme={null}
    // Provide multiple identifiers for better matching
    {
      "address": "123 Main St, Springfield, IL",
      "parcelId": "12-34-567-890",
      "coordinates": {
        "type": "Point",
        "coordinates": [-89.65, 39.78]
      }
    }
    ```
  </Accordion>

  <Accordion title="Cannot modify geospatial information">
    **Cause:** Attempted to modify `address`, `parcelId`, or `coordinates` on existing record

    **Solution:**

    * Geospatial information cannot be changed after record creation
    * Delete the record and create a new one with correct location
    * Or keep existing record and update only non-geospatial fields
  </Accordion>

  <Accordion title="Duplicate resource (409 Conflict)">
    **Cause:** Resource with the same external system ID already exists

    **Error example:**

    ```json theme={null}
    {
      "errors": [
        {
          "field": "externalSystemId",
          "message": "Document upload already exists with external system id 12345"
        }
      ]
    }
    ```

    **Solution:**

    * Use a unique external system ID for each resource
    * Update the existing resource instead of creating a new one
    * Or omit the `externalSystemId` if not needed
  </Accordion>
</AccordionGroup>

### File Upload Errors

<AccordionGroup>
  <Accordion title="File too large">
    **Cause:** Uploaded file exceeds size limit

    **Solution:**

    * Compress or resize files before upload
    * Split large documents into smaller files
    * Contact support if you need higher limits
  </Accordion>

  <Accordion title="Unsupported file format or MIME type">
    **Cause:** File format is not supported for the specified document type

    **Error example:**

    ```json theme={null}
    {
      "errors": [
        {
          "field": "mimeType",
          "message": "MIME type \"image/jpeg\" is not allowed for document type \"elevation_certificate\""
        }
      ]
    }
    ```

    **Solution:**

    * Convert to supported format (PDF for elevation certificates, etc.)
    * Check file extension matches actual file type
    * Verify MIME type is allowed for your document type
  </Accordion>

  <Accordion title="Multipart form data required">
    **Cause:** File upload not sent as multipart/form-data

    **Solution:**

    * Set `Content-Type` header to `multipart/form-data`
    * Use proper file upload mechanism with FormData

    ```javascript theme={null}
    const formData = new FormData();
    formData.append('fileUpload', file);

    fetch('https://app.withforerunner.com/api/v1/files', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer YOUR_API_KEY'
        // Do NOT set Content-Type, let browser set it with boundary
      },
      body: formData
    });
    ```
  </Accordion>
</AccordionGroup>

***

## Implementing Error Handling

### Basic Error Handling

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function makeApiRequest(url, options) {
    try {
      const response = await fetch(url, {
        ...options,
        headers: {
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json',
          ...options.headers
        }
      });

      const data = await response.json();

      // Check for errors in response
      if (data.errors && data.errors.length > 0) {
        console.error('API Errors:', data.errors);

        // Format error messages
        const errorMessages = data.errors
          .map(err => `${err.field}: ${err.message}`)
          .join(', ');

        switch (response.status) {
          case 400:
            throw new Error(`Bad Request: ${errorMessages}`);
          case 401:
            throw new Error('Authentication failed. Check your API key.');
          case 404:
            throw new Error(`Resource not found: ${errorMessages}`);
          case 409:
            throw new Error(`Conflict: ${errorMessages}`);
          case 422:
            throw new Error(`Validation error: ${errorMessages}`);
          case 429:
            throw new Error('Rate limit exceeded. Retry after delay.');
          case 500:
            throw new Error('Server error. Please try again later.');
          default:
            throw new Error(`API error: ${errorMessages}`);
        }
      }

      return data;
    } catch (error) {
      console.error('Request failed:', error);
      throw error;
    }
  }
  ```

  ```python Python theme={null}
  import requests

  def make_api_request(url, method='GET', data=None):
      headers = {
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json'
      }

      try:
          response = requests.request(
              method,
              url,
              headers=headers,
              json=data
          )

          data = response.json()

          # Check for errors in response
          if 'errors' in data and len(data['errors']) > 0:
              # Format error messages
              error_messages = ', '.join(
                  f"{err['field']}: {err['message']}"
                  for err in data['errors']
              )

              status = response.status_code

              if status == 400:
                  raise ValueError(f"Bad Request: {error_messages}")
              elif status == 401:
                  raise PermissionError('Authentication failed. Check your API key.')
              elif status == 404:
                  raise LookupError(f"Resource not found: {error_messages}")
              elif status == 409:
                  raise ValueError(f"Conflict: {error_messages}")
              elif status == 422:
                  raise ValueError(f"Validation error: {error_messages}")
              elif status == 429:
                  raise Exception('Rate limit exceeded. Retry after delay.')
              elif status >= 500:
                  raise Exception('Server error. Please try again later.')
              else:
                  raise Exception(f"API error: {error_messages}")

          return data

      except requests.exceptions.RequestException as e:
          print(f'Request failed: {e}')
          raise
  ```
</CodeGroup>

### Exponential Backoff for Retries

Implement exponential backoff for transient errors (`500`, `502`, `503`, `504`):

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function makeRequestWithRetry(url, options, maxRetries = 3) {
    let lastError;

    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        const response = await fetch(url, options);
        const data = await response.json();

        // Don't retry client errors (4xx)
        if (response.status >= 400 && response.status < 500) {
          if (data.errors && data.errors.length > 0) {
            const errorMessages = data.errors
              .map(err => `${err.field}: ${err.message}`)
              .join(', ');
            throw new Error(errorMessages);
          }
          throw new Error('Client error occurred');
        }

        // Retry server errors (5xx)
        if (response.status >= 500) {
          throw new Error(`Server error: ${response.status}`);
        }

        // Check for errors even with 200 status
        if (data.errors && data.errors.length > 0) {
          const errorMessages = data.errors
            .map(err => `${err.field}: ${err.message}`)
            .join(', ');
          throw new Error(errorMessages);
        }

        return data;

      } catch (error) {
        lastError = error;

        // Only retry on server errors (5xx)
        if (error.message.includes('Server error') && attempt < maxRetries - 1) {
          // Exponential backoff: 1s, 2s, 4s
          const delay = Math.pow(2, attempt) * 1000;
          console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
          await new Promise(resolve => setTimeout(resolve, delay));
        } else {
          break;
        }
      }
    }

    throw new Error(`Request failed after ${maxRetries} attempts: ${lastError.message}`);
  }
  ```

  ```python Python theme={null}
  import time
  import requests

  def make_request_with_retry(url, method='GET', data=None, max_retries=3):
      last_error = None

      for attempt in range(max_retries):
          try:
              response = requests.request(
                  method,
                  url,
                  headers={
                      'Authorization': 'Bearer YOUR_API_KEY',
                      'Content-Type': 'application/json'
                  },
                  json=data
              )

              response_data = response.json()

              # Don't retry client errors (4xx)
              if 400 <= response.status_code < 500:
                  if 'errors' in response_data and len(response_data['errors']) > 0:
                      error_messages = ', '.join(
                          f"{err['field']}: {err['message']}"
                          for err in response_data['errors']
                      )
                      raise ValueError(error_messages)
                  raise ValueError('Client error occurred')

              # Retry server errors (5xx)
              if response.status_code >= 500:
                  raise Exception(f"Server error: {response.status_code}")

              # Check for errors even with 200 status
              if 'errors' in response_data and len(response_data['errors']) > 0:
                  error_messages = ', '.join(
                      f"{err['field']}: {err['message']}"
                      for err in response_data['errors']
                  )
                  raise ValueError(error_messages)

              return response_data

          except Exception as e:
              last_error = e

              # Only retry on server errors (5xx)
              is_server_error = isinstance(e, Exception) and 'Server error' in str(e)
              if is_server_error and attempt < max_retries - 1:
                  # Exponential backoff: 1s, 2s, 4s
                  delay = 2 ** attempt
                  print(f"Retry attempt {attempt + 1} after {delay}s")
                  time.sleep(delay)
              else:
                  break

      raise Exception(f"Request failed after {max_retries} attempts: {last_error}")
  ```
</CodeGroup>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Handle all error cases">
    Don't just handle successful responses. Plan for and gracefully handle all possible error scenarios.
  </Accordion>

  <Accordion title="Implement exponential backoff">
    For `5xx` errors and timeouts, use exponential backoff with jitter to avoid overwhelming the server.
  </Accordion>

  <Accordion title="Log errors comprehensively">
    Include timestamps, request details, and full error responses in your logs for easier debugging.
  </Accordion>

  <Accordion title="Don't retry client errors">
    `4xx` errors indicate issues with your request. Retrying won't help - fix the request instead.
  </Accordion>

  <Accordion title="Monitor API health">
    Track error rates and response times to detect issues early.
  </Accordion>

  <Accordion title="Validate before sending">
    Validate data client-side before making API requests to catch issues early and reduce unnecessary requests.
  </Accordion>
</AccordionGroup>

***

## Debugging

<Tabs>
  <Tab title="Enable verbose logging">
    Log all requests and responses to diagnose issues:

    ```javascript theme={null}
    console.log('Request:', url, options);
    console.log('Response:', response);
    ```
  </Tab>

  <Tab title="Test with cURL">
    Isolate issues by testing endpoints with cURL:

    ```bash theme={null}
    curl -v https://app.withforerunner.com/api/v1/properties \
      -H "Authorization: Bearer YOUR_API_KEY"
    ```
  </Tab>

  <Tab title="Verify environments">
    Ensure you're using the correct base URL for your environment (staging vs production)
  </Tab>

  <Tab title="Check API key validity">
    Confirm your API key is valid by making a simple GET request to `/api/v1/properties`
  </Tab>
</Tabs>

***

## Getting Help

If you're experiencing issues not covered in this guide:

<Steps>
  <Step title="Check API status">
    Verify there are no ongoing incidents or maintenance
  </Step>

  <Step title="Review your request">
    * Confirm all required parameters are included
    * Verify data types and formats match documentation
    * Check authentication header is correct
  </Step>

  <Step title="Gather debugging information">
    * Request ID from error response
    * Full error message and status code
    * Example request that reproduces the issue
    * Timestamp when error occurred
  </Step>

  <Step title="Contact support">
    Email [engineering@withforerunner.com](mailto:engineering@withforerunner.com) with your debugging information
  </Step>
</Steps>

***
