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

# Error Handling

> How to handle errors from the Influship API

Authenticated API errors use a consistent JSON shape. Use the HTTP status code to decide what to do, and the error body for details. Unauthenticated x402 and MPP payment challenges carry their details in response headers; see the [x402](/guides/x402) and [MPP](/guides/mpp) guides.

| Before you start | Details |
| - | - |
| Auth | Use your endpoint’s credential |
| Resource | All API resources |
| Cost | API-key/OAuth 4xx and 5xx are not charged; settled payments differ |
| Limits | Apply attempt and deadline limits; read Retry-After |

A no-key request to a paid endpoint, including search, can return HTTP 402 with an empty JSON body. Read `PAYMENT-REQUIRED` or `WWW-Authenticate` for the challenge instead of requiring an `error` object.

Malformed TikTok video URLs return HTTP 400; correct the URL before retrying.

## Error response shape

Authenticated API errors return this structure:

```json theme={null}
{
  "error": {
    "code": "error_code_here",
    "message": "Human-readable description"
  }
}
```

The `code` field is a stable, machine-readable string. The `message` field is human-readable and may change between versions — don't match against it programmatically.

Optional fields inside `error` provide more context: `param` identifies an invalid parameter, and `details` contains structured error information. Read the HTTP response status even when `error.status_code` is absent.

Save the `X-Request-Id` response header when reporting a failed request to support. `error.request_id` is optional; your client should not require a request ID or timestamp in the JSON body.

Most authenticated 402 responses use `payment_required` and also include a `reason_code` and `next_step`. Branch on `code` first. For `payment_required`, then branch on `reason_code` when deciding which recovery action to show. Search can instead return `insufficient_credits` when remaining credits cannot cover the request; that body does not include `reason_code` or `next_step`.

## Error code reference

| Status | Code | Meaning | What to do |
| - | - | - | - |
| 400 | `validation_error` | Bad request body or params | Fix the request. Check required fields and types. |
| 401 | `unauthorized` | Missing or invalid API key | Check your `X-API-Key` header. See [Authentication](/guides/authentication). |
| 402 | `payment_required` | Account billing requires attention | Open the billing dashboard and follow the supplied next step. |
| 402 | `insufficient_credits` | Remaining credits cannot cover this request | Add a payment method, then retry. Do not retry automatically. |
| 404 | `not_found` | Resource doesn't exist. For live/raw profile scrapes, the account is missing, deleted, or returns no public data | Check the ID or username. |
| 404 | `seed_not_found` | A lookalike seed is not available for similarity matching | Choose another seed. Retrying the same seed does not change the result. |
| 422 | `validation_error` | Invalid fields or stale saved-shortlist version | Fix fields, or reopen and review the list before submitting its current version. |
| 429 | `rate_limit_exceeded` | Over your per-minute or per-hour budget | Read `Retry-After` header or wait for the reset window. See [Rate Limits](/concepts/quotas-and-limits). |
| 500 | `internal_error` | The API could not complete the request | Retry with backoff. If persistent, contact support. |
| 502 | `upstream_contract_broken` | The API could not process the source data | Don't retry automatically. Fall back to cached data and contact support if it persists. |
| 503 | `service_unavailable` | A live data upstream is temporarily unavailable | Retry after `Retry-After` if present, then back off with jitter. |

## Handling errors in code

The SDK retries some failures, including 502 responses, twice by default. Disable those retries with `maxRetries: 0` when applying the status-specific policy below, so your catch block sees the first failure. Add bounded retries only for the statuses your application handles as temporary.

<CodeGroup>
  ```typescript SDK theme={null}
  import Influship, { APIError, RateLimitError } from 'influship';

  const client = new Influship({ maxRetries: 0 });

  try {
    const results = await client.search.create({
      query: 'fitness creators',
    });
  } catch (error) {
    if (error instanceof RateLimitError) {
      // Your Influship API key hit its account-level quota.
      const retryAfter = error.headers?.get('retry-after');
      console.log(`Rate limited. Retry after ${retryAfter}s`);
    } else if (error instanceof APIError && error.status === 503) {
      // Temporary issue while fetching fresh live data.
      const retryAfter = error.headers?.get('retry-after');
      console.log(`Service unavailable. Retry after ${retryAfter ?? 'a short backoff'}s`);
    } else if (error instanceof APIError && error.status === 502) {
      // Source data could not be processed; do not retry automatically.
      console.log('Upstream data unusable. Do not retry; use cached data instead.');
    } else if (error instanceof APIError) {
      console.log(`API error ${error.status}: ${error.message}`);
    } else {
      throw error;
    }
  }
  ```

  ```bash cURL theme={null}
  curl -w "\n%{http_code}\n" \
    -X POST https://api.influship.com/v1/search \
    -H 'X-API-Key: YOUR_API_KEY' \
    -H 'Content-Type: application/json' \
    -d '{"query": "fitness creators", "limit": 10}'
  ```
</CodeGroup>

The SDK throws typed error classes, so you can catch specific error types and handle them differently. For raw HTTP, check the status code of the response.

### Bounded retries

With SDK retries disabled, this helper makes at most three attempts for 429, 500, or 503 responses. It stops immediately for other statuses, including 402 and 502. It accepts both seconds and HTTP dates in `Retry-After`; when the requested delay exceeds a minute, it returns the error for your job scheduler to handle.

```typescript theme={null}
async function requestWithRetry<T>(operation: () => Promise<T>): Promise<T> {
  for (let attempt = 0; ; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (
        !(error instanceof APIError) ||
        ![429, 500, 503].includes(error.status ?? 0) ||
        attempt >= 2
      ) {
        throw error;
      }

      const header = error.headers?.get('retry-after');
      const seconds = header === null || header === undefined ? NaN : Number(header);
      const requestedDelay = Number.isFinite(seconds)
        ? seconds * 1000
        : header ? Date.parse(header) - Date.now() : NaN;
      const delay = Number.isFinite(requestedDelay)
        ? Math.max(0, requestedDelay)
        : 1000 * 2 ** attempt + Math.random() * 1000;
      if (delay > 60_000) throw error;
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
}

const results = await requestWithRetry(() =>
  client.search.create({ query: 'fitness creators', limit: 5 }),
);
```

Use this helper with the `APIError` import and `maxRetries: 0` client above. Set a total deadline for background jobs as well as an attempt limit. For x402 and MPP, check payment settlement before retrying: another attempt can require another payment.

## Rate limit headers

Read the rate-limit headers returned with your request to track the applicable budget before hitting 429. Do not require these headers on unauthenticated or failed requests. See [Rate Limits & Tiers](/concepts/quotas-and-limits) for the full header reference and trust tier table.

## Implementation advice

For production integrations, handle 429 and retryable 503 responses with exponential backoff. A simple strategy: wait `2^attempt` seconds, capped at 60 seconds, with jitter. If `Retry-After` is present, use it as the first delay.

Treat 429 as your API key's account-level rate limit. Treat 503 `service_unavailable` from live data endpoints as temporary unavailability, not as your quota being exhausted. Honor `Retry-After` when present, including for Instagram post and transcript lookups.

Instagram post lookups use the same `503 service_unavailable` response when Instagram throttles access to the post. Wait before retrying, keep a total retry deadline, and use your last cached result if that deadline expires. A temporary access failure does not establish that the post was deleted.

Live Instagram profile requests also return `503 service_unavailable` when a complete requested response cannot be recovered within the request budget. Preserve cached profile data during this failure; do not treat it as an empty or inactive account. HTTP 401 from the Influship API still means your API credentials need attention.

For lookalike requests, treat `404 seed_not_found` as a seed-level result rather than a temporary outage. Select another seed; don't retry the same request with backoff.

Do not fold `502 upstream_contract_broken` into your `503` retry path. A `503` indicates temporary unavailability; honor `Retry-After` when present and use bounded backoff. A `502 upstream_contract_broken` means the API could not process the source data. Do not retry automatically; use your last cached value and contact support if the issue persists. Retrying an already-settled x402 or MPP request requires another payment.

Treat 402 as a billing issue that needs human intervention. Do not retry it automatically. Surface `payment_required` to your ops team or billing dashboard, and treat `insufficient_credits` as a prompt to add a payment method. Temporary failures while checking billing state return retryable 503 responses instead.


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