Skip to content

Error Handling

Most OpenProspect application errors return a stable machine-readable code, a human-readable message, and optional machine-readable fields. Idempotency conflicts group their request metadata under details. Request validation and request-size rejection instead use the detail envelopes documented below.

{
  "code": "AUTHORIZATION_ERROR",
  "message": "Missing required scopes: orders:write",
  "required_scopes": ["orders:write"],
  "user_scopes": ["orders:read"]
}

Status Codes

Status Category Cause Resolution
400 Bad request The request is syntactically valid but not acceptable for the operation Correct the request fields
401 Authentication Missing, malformed, expired, or revoked API key Send an active API key as a bearer token
403 Authorization The API key lacks a required scope or resource access Add the required scope or use a different key
404 Not found The resource does not exist or is not visible to the key owner Check the identifier and organization context
409 Conflict The request conflicts with current resource state Refresh state and retry with current data
413 Content too large The serialized request exceeds the deployment's body-size limit Reduce field sizes or split the request into smaller orders
422 Validation Request body, path, or query parameters failed validation Use the schema from the interactive API
429 Rate limit Too many requests in a short time window Back off and retry later
500 Server Unexpected server error Retry with backoff and contact support if it persists
503 Service unavailable A dependent service was temporarily unavailable Retry with backoff using the same idempotency key for POST requests

Error Codes

Code Status Description Cause Resolution
UNAUTHORIZED 401 The API key could not be authenticated Missing or invalid bearer token Set Authorization: Bearer lnc_live_...
AUTHORIZATION_ERROR 403 The key lacks a required scope Scope mismatch Request the missing scope
ORDER_NOT_FOUND 404 The order is unavailable Wrong ID or inaccessible organization Use GET /api/v1/orders to confirm visible orders
ORDER_RESULTS_NOT_READY 404 Order results are not available yet The order has not reached COMPLETED Poll GET /api/v1/orders/{order_id} until status is COMPLETED
PROSPECT_SEARCH_NOT_FOUND 404 The prospect search is unavailable Wrong ID or inaccessible organization Use GET /api/v1/prospect-searches
BLACKLIST_NOT_FOUND 404 The blacklist is unavailable Wrong ID or inaccessible organization Use GET /api/v1/blacklists
IDEMPOTENCY_CONFLICT 409 An idempotency key was reused with a different request body Same Idempotency-Key, different payload Retry with a new idempotency key
SEARCH_PROFILE_CREATION_NOT_FOUND 404 The profile creation request is unavailable Wrong ID or inaccessible organization Check profile_creation_id and API key organization
SEARCH_PROFILE_CREATION_FAILED 200 Profile creation reached FAILED status AI profile synthesis could not complete Retry with a new idempotency key or contact support

Retry Rules

Status Retry
400, 401, 403, 404, 413, 422 Do not retry without changing the request, except ORDER_RESULTS_NOT_READY status polling
409 Retry only after refreshing resource state
429 Retry with exponential backoff
500, 502, 503, 504 Retry with exponential backoff and alert on repeated failures

Async Statuses

Profile creation and orders are asynchronous. A polling request can return 200 OK while the resource status is still non-terminal.

Resource Retry/poll while Stop when
Search profile creation PENDING, RUNNING SUCCEEDED, FAILED
Order RECEIVED, ACCEPTED, IN_PROGRESS COMPLETED, REJECTED, FAILED, CANCELLED

Fetch order results only after the order is COMPLETED.

Order Integration Recovery

Use the HTTP status and machine-readable error code together when a code is present. Size-limit and FastAPI validation responses do not include one.

Status or code What it means Recovery
401 / UNAUTHORIZED The API key is missing, malformed, expired, or revoked Send an active key as Authorization: Bearer ..., then validate it with GET /api/v1/auth/validate
403 / AUTHORIZATION_ERROR The key lacks a required scope or resource access Compare required_scopes with user_scopes, then use a correctly scoped key
404 / ORDER_NOT_FOUND The order ID is wrong or belongs to another organization List visible orders and confirm the key's organization
404 / ORDER_RESULTS_NOT_READY The order exists but has not reached COMPLETED Poll order status while it is RECEIVED, ACCEPTED, or IN_PROGRESS; do not create a replacement order
409 / IDEMPOTENCY_CONFLICT The key was already used with a different body Use a new key for the changed body; reuse the original key only for an identical retry
413 (no machine code) The serialized body exceeds the configured request-size limit; deployments default to 10 MB Reduce field sizes or split the companies across smaller orders
422 (no machine code) A required header, field, path, or query value is invalid; inspect each entry in detail Check profile_id, companies, features, and Idempotency-Key; omit briefing_quantity for enrichment

Order statuses are separate from HTTP errors. Stop polling on every terminal status:

  • COMPLETED: retrieve every result page.
  • REJECTED: inspect admin_notes; if it is null, contact support with the order_id.
  • FAILED: contact support with the order_id.
  • CANCELLED: stop without requesting results.

Request Validation and Size Limits

FastAPI request validation returns 422 with a bounded detail array. It does not include code or message:

{
  "detail": [
    {
      "type": "missing",
      "loc": ["header", "Idempotency-Key"],
      "msg": "Field required"
    }
  ]
}

The request-size middleware runs before schema validation. Deployments accept at most 10 MB by default (10,485,760 bytes), although operators can configure a different limit. An oversized request returns 413 with only detail:

{
  "detail": "Request payload too large. Maximum allowed: 10485760 bytes (10MB)"
}

The 10,000-company schema maximum is therefore not a guaranteed batch size. Serialize the request, keep it below the active byte limit, and split larger inputs into multiple orders.

Idempotency Conflicts

POST /api/v1/search-profile-creations and POST /api/v1/orders require Idempotency-Key. If the same key and same body are submitted again, the API returns the existing resource. If the same key is reused with a different body, the API returns 409 Conflict.

Resolution: generate a new idempotency key for the changed request.

{
  "code": "IDEMPOTENCY_CONFLICT",
  "message": "Idempotency key was already used with a different request payload.",
  "details": {
    "idempotency_key": "openprospect-profile-2026-07-02"
  }
}

Profile Creation Failures

Profile creation is asynchronous. A failed generation is reported by the status endpoint, not as a failed polling HTTP response.

{
  "profile_creation_id": "f7a5a926-4d42-4e30-9f40-61e5da52f7aa",
  "status": "FAILED",
  "profile_id": null,
  "error_code": "search_config_generation_failed",
  "error_message": "Profile generation failed. Please retry with a new idempotency key or contact support."
}

Resolution: create a new profile creation request with a new idempotency key. If the second request fails with the same input, contact support with the profile_creation_id.

Error Handling Examples

status="$(
  curl -sS -o /tmp/openprospect-response.json -w "%{http_code}" \
    https://api.openprospect.io/api/v1/orders \
    -H "Authorization: Bearer ${OPENPROSPECT_API_KEY}"
)"

if [ "$status" -ge 400 ]; then
  cat /tmp/openprospect-response.json
  exit 1
fi

cat /tmp/openprospect-response.json
import os
import time
from typing import Any

import httpx

api_key = os.environ["OPENPROSPECT_API_KEY"]

def request_with_backoff(url: str) -> dict[str, Any]:
    for attempt in range(3):
        response = httpx.get(
            url,
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=30.0,
        )
        if response.status_code < 400:
            return response.json()
        if response.status_code not in {429, 500, 502, 503, 504}:
            raise RuntimeError(response.text)
        time.sleep(2**attempt)
    raise RuntimeError("OpenProspect request failed after retries")

print(request_with_backoff("https://api.openprospect.io/api/v1/orders"))
async function requestWithBackoff(url) {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.OPENPROSPECT_API_KEY}` },
    });

    if (response.ok) {
      return response.json();
    }

    if (![429, 500, 502, 503, 504].includes(response.status)) {
      throw new Error(await response.text());
    }

    await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** attempt));
  }

  throw new Error("OpenProspect request failed after retries");
}

console.log(await requestWithBackoff("https://api.openprospect.io/api/v1/orders"));
interface ApiError {
  code: string;
  message: string;
  details?: Record<string, unknown>;
  [key: string]: unknown;
}

async function requestWithBackoff<T>(url: string): Promise<T> {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.OPENPROSPECT_API_KEY}` },
    });

    if (response.ok) {
      return (await response.json()) as T;
    }

    if (![429, 500, 502, 503, 504].includes(response.status)) {
      const error = (await response.json()) as ApiError;
      throw new Error(`${error.code}: ${error.message}`);
    }

    await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** attempt));
  }

  throw new Error("OpenProspect request failed after retries");
}
using System.Net;
using System.Net.Http.Headers;

var apiKey = Environment.GetEnvironmentVariable("OPENPROSPECT_API_KEY")
    ?? throw new InvalidOperationException("OPENPROSPECT_API_KEY is not set.");

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);

for (var attempt = 0; attempt < 3; attempt++)
{
    var response = await client.GetAsync("https://api.openprospect.io/api/v1/orders");

    if (response.IsSuccessStatusCode)
    {
        Console.WriteLine(await response.Content.ReadAsStringAsync());
        break;
    }

    if (response.StatusCode is not HttpStatusCode.TooManyRequests and < HttpStatusCode.InternalServerError)
    {
        throw new InvalidOperationException(await response.Content.ReadAsStringAsync());
    }

    await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}