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: inspectadmin_notes; if it is null, contact support with theorder_id.FAILED: contact support with theorder_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:
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¶
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)));
}