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

# Handling errors

> Read problem details, decide whether to fix, wait or retry, and retry safely with backoff.

Every error from the API is a problem details document ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)) with the media type `application/problem+json`:

```json theme={null}
{
  "type": "https://docs.pinakelai.com/errors/jurisdiction-unavailable",
  "title": "Jurisdiction unavailable",
  "status": 503,
  "detail": "Register AD is being reloaded. Retry after the Retry-After interval.",
  "request_id": "req_01M7Q3ZQ2K9W5E1N7R4T8V2X6B"
}
```

* **Branch on `type`.** It is a stable URL, and it opens the page that documents the error. `title` and `detail` are for people and can be reworded at any time.
* **Log `request_id`.** It equals the `X-Request-Id` header, and support can find the request with it.
* `invalid-request` adds an `errors` array listing every problem in the request; 429 responses add `violated_policies`. See the [errors reference](https://docs.pinakelai.com/api-reference/errors.md) for every type.

## Fix, wait, or retry

| Status | Types | What to do |
| - | - | - |
| 400 | `invalid-request`, `invalid-cursor`, `cursor-mismatch` | Fix the request. Do not retry it unchanged. |
| 401, 403 | `unauthenticated`, `key-revoked`, `product-not-enabled` | Stop and alert a person. Retrying cannot succeed, and repeated failures from one IP address trigger a 5-minute block. |
| 404 | `not-found` | An answer, not a failure: the record or register does not exist. Do not retry. |
| 404, 405, 410 | `unknown-endpoint`, `method-not-allowed`, `removed` | Fix the URL or method. `removed` means an old path; move to `/v1`. |
| 413, 415 | `payload-too-large`, `unsupported-media-type` | Fix the batch body: JSON, at most 64 KB. |
| 422 | `query-too-broad`, `query-timeout` | Narrow the query. Retrying the same query gives the same result. |
| 429 | `rate-limited`, `quota-exceeded` | Wait for `Retry-After`, then retry. For `quota-exceeded`, wait for the next month or ask for a higher quota. |
| 500 | `internal-error` | Retry once or twice with backoff; report the `request_id` if it persists. |
| 503 | `unavailable`, `jurisdiction-unavailable`, `search-index-unavailable` | Wait for `Retry-After` (or back off), then retry. |

The API never returns 504. A search that runs out of time is a 422 `query-timeout`; any other timeout is a 503 `unavailable`. A timeout somewhere between you and the API shows up as a network error or a 502; treat it like a 503.

## Retrying safely

Every operation is read-only, including `POST /v1/trademarks/batch`, so any request can be retried without side effects. Retry only on 429, 500 and 503 (and network errors), respect `Retry-After` when present, and otherwise back off exponentially with jitter.

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

import requests

RETRYABLE = {429, 500, 502, 503}


def call(session: requests.Session, method: str, url: str, *, attempts: int = 5, **kwargs) -> requests.Response:
    """Send a request, retrying 429/5xx and network errors with backoff."""
    for attempt in range(attempts):
        try:
            resp = session.request(method, url, timeout=60, **kwargs)
        except requests.ConnectionError:
            resp = None
        if resp is not None and resp.status_code not in RETRYABLE:
            return resp  # success, or an error that retrying cannot fix
        if attempt == attempts - 1:
            break
        retry_after = resp.headers.get("Retry-After") if resp is not None else None
        delay = float(retry_after) if retry_after else min(60.0, 2 ** attempt) + random.random()
        time.sleep(delay)
    if resp is None:
        raise ConnectionError(f"{method} {url}: no response after {attempts} attempts")
    return resp


# session and API as in the Quickstart
resp = call(session, "GET", f"{API}/trademarks", params={"jurisdiction": "AD", "number": "48000"})
if resp.headers.get("Content-Type", "").startswith("application/problem+json"):
    problem = resp.json()
    raise RuntimeError(f"{problem['type']} ({problem['request_id']}): {problem['detail']}")
```

```javascript JavaScript theme={null}
const RETRYABLE = new Set([429, 500, 502, 503]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// Send a request, retrying 429/5xx and network errors with backoff.
async function call(url, init = {}, attempts = 5) {
  let res;
  for (let attempt = 0; attempt < attempts; attempt++) {
    try {
      res = await fetch(url, init);
    } catch {
      res = undefined; // network error
    }
    if (res && !RETRYABLE.has(res.status)) return res;
    if (attempt === attempts - 1) break;
    const retryAfter = res?.headers.get("Retry-After");
    await sleep(retryAfter ? Number(retryAfter) * 1000 : Math.min(60_000, 2 ** attempt * 1000) + Math.random() * 1000);
  }
  if (!res) throw new Error(`${url}: no response after ${attempts} attempts`);
  return res;
}

// API and headers as in the Quickstart
const res = await call(`${API}/trademarks?jurisdiction=AD&number=48000`, { headers });
if (res.headers.get("Content-Type")?.startsWith("application/problem+json")) {
  const problem = await res.json();
  throw new Error(`${problem.type} (${problem.request_id}): ${problem.detail}`);
}
```

`Retry-After` is in seconds. For a register that is reloading it is at least 900 (15 minutes): rather than sleeping that long inside a request, record the failure and let your next scheduled run try again.

## Errors without a problem body

Requests stopped by our edge protection, before they reach the API, can get a 429 or 403 without a JSON body and without `RateLimit` or `Retry-After` headers. Treat a 429 without a body like `rate-limited` and back off, starting at a few seconds.

## Validation errors

A 400 `invalid-request` lists every problem at once, so you can fix them all in one pass:

```json theme={null}
{
  "type": "https://docs.pinakelai.com/errors/invalid-request",
  "title": "Invalid request",
  "status": 400,
  "detail": "The request has invalid parameters; see errors.",
  "request_id": "req_01M7Q3ZR5T1V8X2B6C9D3F7G4H",
  "errors": [
    { "code": "invalid-parameter", "detail": "nice_class must be an integer from 1 to 45.", "parameter": "nice_class" },
    { "code": "reserved-parameter", "detail": "q is reserved. Did you mean mark_text_phrase?", "parameter": "q" }
  ]
}
```

Each entry has a `code`, a `detail`, and either `parameter` (a query parameter name) or `pointer` (a JSON Pointer into the batch request body, such as `/ids/3`). [Searching](https://docs.pinakelai.com/guides/searching.md#when-a-search-is-refused) lists the codes.


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