Piloterr

Error Handling

Understand Piloterr API error responses and learn how to handle each case gracefully.

Every request to the Piloterr API returns a standard HTTP status code. This guide covers what each code means, when credits are consumed, and how to handle failures in your integration.

Response format

Successful responses return a JSON object with the data for that endpoint. Error responses follow a consistent structure:

{
  "error": "Human-readable description of what went wrong"
}

Always check the HTTP status code before reading the body.


Status codes

200 Success

Billed. The request completed successfully. Credits are deducted from your balance.

const res = await fetch(url, { headers: { "x-api-key": API_KEY } })

if (res.status === 200) {
  const data = await res.json()
  // process data
}

201 Job Created

Billed. An asynchronous job was created and accepted. Credits are deducted immediately when the job is queued.

202 Accepted (async)

Not billed yet. The request was accepted and queued for processing. Use the returned job ID to poll for the result. Credits are only billed when the job completes successfully.

400 Bad Request

Not billed. Your request contains invalid or missing parameters.

Common causes:

  • A required query parameter is missing
  • A parameter value has the wrong type or format
  • The request body is malformed

What to do: Re-read the endpoint documentation and verify every required parameter. Log the full request URL to spot typos.

if (res.status === 400) {
  const { error } = await res.json()
  console.error("Bad request:", error)
  // do not retry, fix the parameters first
}

401 Unauthorized

Not billed. Returned for authentication issues and rate limiting. Check the error message to distinguish between them.

Your x-api-key header is missing or contains an unrecognized value.

Fix: Copy your key from your dashboard → API Keys and make sure the x-api-key header is present on every request.

The key exists but has been deactivated.

Fix: Go to your dashboard → API Keys and activate the key, or generate a new one.

The key has passed its expiry date.

Fix: Generate a new key or extend the expiry from your dashboard.

You have sent too many requests in a short window.

Fix: Back off and retry after a delay. If you hit this regularly, consider upgrading your plan.

if (res.status === 401) {
  const { error } = await res.json()
  if (error.toLowerCase().includes("rate limit")) {
    await new Promise(r => setTimeout(r, 2000))
    // retry
  }
}

402 Payment Required

Not billed. Your credit balance is empty. The request was not executed.

What to do:

  1. Top up your balance from your dashboard.
  2. Or enable Auto Top-Up to prevent this from happening again.

Auto Top-Up only triggers when your balance drops below the configured threshold. It never runs on a fixed schedule or at a specific time. A built-in 72-hour cooldown also prevents multiple charges in a short period: once a top-up fires, it cannot trigger again for 72 hours regardless of how your balance moves. If a charge fails, Auto Top-Up is automatically disabled to protect your account.

Do not retry a 402 response immediately. The call will fail again until you add credits. Set up an alert so your team is notified when this happens.

if (res.status === 402) {
  // alert your team or trigger an auto top-up flow
  throw new Error("Insufficient credits. Top up at https://app.piloterr.com")
}

403 Forbidden

Not billed. Your key does not have permission to access this endpoint.

What to do: Verify that your key's permissions cover the endpoint you are calling. Contact support if you believe this is a mistake.

404 Not Found

May be billed (endpoint-specific). The resource was not found. Check the individual endpoint documentation to confirm whether this response is charged.

if (res.status === 404) {
  // handle "no result" gracefully, not as a fatal error
  return null
}

500 Internal Server Error

Not billed. An unexpected error occurred on our side.

What to do: Retry with exponential backoff. If the issue persists, contact support.

async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const res = await fetch(url, options)

    if (res.status !== 500 || attempt === maxRetries) return res

    const delay = 500 * 2 ** attempt
    console.warn(`500 error, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`)
    await new Promise(r => setTimeout(r, delay))
  }
}

Complete error handler

A single function that covers every status code:

interface ApiResult<T> {
  data: T | null
  error: string | null
  status: number
  billed: boolean
}

async function apiCall<T>(url: string, apiKey: string): Promise<ApiResult<T>> {
  const res = await fetch(url, {
    headers: { "x-api-key": apiKey },
  })

  // 200 and 201 are always billed; 404 billing is endpoint-specific
  const billed = res.status === 200 || res.status === 201

  if (res.status === 200) {
    return { data: await res.json() as T, error: null, status: 200, billed }
  }

  const body = await res.json().catch(() => ({ error: "Unknown error" }))
  const error = body?.error ?? "Unknown error"

  switch (res.status) {
    case 400:
      console.error("[400] Bad request, fix your parameters:", error)
      break
    case 401:
      if (error.toLowerCase().includes("rate limit")) {
        console.warn("[401] Rate limit, back off and retry")
      } else {
        console.error("[401] Auth error, check your API key:", error)
      }
      break
    case 402:
      console.error("[402] No credits remaining. Top up at https://app.piloterr.com")
      break
    case 403:
      console.error("[403] Forbidden, check your key permissions")
      break
    case 404:
      console.warn("[404] Not found, no result for this request")
      break
    case 500:
      console.error("[500] Server error, retry with backoff")
      break
    default:
      console.error(`[${res.status}] Unexpected status:`, error)
  }

  return { data: null, error, status: res.status, billed }
}

Retry strategy

StatusRetry?Strategy
400NoFix the request first
401 (rate limit)YesWait 2–5 s, then retry
401 (auth)NoFix the API key
402NoAdd credits first
403NoCheck permissions
404NoHandle as empty result
500YesExponential backoff (max 3 attempts)

Billing summary

StatusBilled
200Yes
201Yes
404Endpoint-specific (check the endpoint docs)
All other codesNo

See the Credits page for a full cost breakdown per endpoint. For broader integration advice, see Best Practices.

On this page