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

# Errors

> Error response format, catalog, and idempotency

Every Signa API error follows a consistent structure inspired by RFC 9457. Errors are wrapped under an `"error"` key with machine-readable fields for programmatic handling.

## Error Response Structure

```json theme={null}
{
  "error": {
    "type": "not_found",
    "title": "Resource not found",
    "status": 404,
    "detail": "Trademark tm_xxx does not exist.",
    "instance": "/v1/trademarks/tm_xxx",
    "suggestion": "Check the trademark ID. Use GET /v1/trademarks?q=... to find marks by text.",
    "retryable": false,
    "retry_after": null
  },
  "request_id": "req_abc123"
}
```

| Field         | Type            | Description                                                                                                                |
| ------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `type`        | string          | Snake\_case error type slug (stable identifier for programmatic use, e.g. `not_found`, `validation_error`, `rate_limited`) |
| `title`       | string          | Human-readable error title                                                                                                 |
| `status`      | integer         | HTTP status code                                                                                                           |
| `detail`      | string          | Specific error description for this instance                                                                               |
| `instance`    | string          | Request path that caused the error                                                                                         |
| `suggestion`  | string          | Actionable fix guidance                                                                                                    |
| `retryable`   | boolean         | Whether the client should retry                                                                                            |
| `retry_after` | integer or null | Seconds to wait before retry                                                                                               |
| `request_id`  | string          | Unique request identifier for support (top-level, alongside `error`)                                                       |

***

## Error Catalog

| Status | Type Slug                | Retryable | Title                   | Example Message                                                                                                                        | Common Cause                                                                                                                           | Fix                                                                                                                                                                                                                                       |
| ------ | ------------------------ | --------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `validation_error`       | No        | Validation failed       | "Parameter 'limit' must be between 1 and 100."                                                                                         | Invalid query parameter, missing required field, malformed JSON body                                                                   | Check the parameter types and ranges. See the endpoint documentation for accepted values.                                                                                                                                                 |
| 400    | `id_type_mismatch`       | No        | ID type mismatch        | "Expected an owner ID (own\_\*) but received tm\_abc123."                                                                              | Using a trademark ID where an owner ID is expected (or vice versa)                                                                     | Use the correct ID prefix for the endpoint. Owner endpoints expect `own_*`, trademark endpoints expect `tm_*`, etc.                                                                                                                       |
| 400    | `cursor_expired`         | No        | Cursor expired          | "The pagination cursor has expired. Start a new query."                                                                                | Pagination cursor is more than 24 hours old                                                                                            | Start a fresh pagination session. For long-running syncs, checkpoint and resume within the 24-hour window.                                                                                                                                |
| 401    | `unauthorized`           | No        | Authentication required | "Invalid API key."                                                                                                                     | Missing `Authorization` header, invalid key format, expired key, revoked key                                                           | Check that you are sending `Authorization: Bearer sig_YOUR_KEY` with a valid key. Verify the key has not expired or been revoked.                                                                                                         |
| 403    | `forbidden`              | No        | Insufficient scope      | "API key does not have the 'trademarks:read' scope."                                                                                   | The API key is valid but lacks the required scope for this endpoint                                                                    | Create a new API key with the required scopes, or update the existing key's scopes via `PATCH /v1/organization/api-keys/{id}`.                                                                                                            |
| 403    | `plan_upgrade_required`  | No        | Plan upgrade required   | "Portfolios is not available on your current plan. Upgrade to access this feature."                                                    | The endpoint belongs to a feature that is not included in your current plan (e.g. portfolios, watches, saved searches, alerts, events) | Upgrade your plan to gain access. Contact sales if you need this capability enabled. Not retryable: the response will not change without a plan change.                                                                                   |
| 404    | `not_found`              | No        | Resource not found      | "Trademark tm\_xxx does not exist."                                                                                                    | The ID does not exist, was deleted, or belongs to a different resource type                                                            | Verify the ID is correct. Use search to find the resource if you do not have the exact ID.                                                                                                                                                |
| 409    | `conflict`               | No        | Conflict                | "Idempotency key 'abc123' was already used with a different request body."                                                             | Reusing an `Idempotency-Key` header with a different request body                                                                      | Generate a new unique idempotency key for each distinct request. If retrying the same request, use the same key AND the same body.                                                                                                        |
| 409    | `idempotency_processing` | Yes       | Idempotency key in use  | "A request with this idempotency key is currently being processed."                                                                    | A retry with the same `Idempotency-Key` landed while the original request was still in flight                                          | Wait briefly for the original request to finish, then retry with the same key to receive its cached result.                                                                                                                               |
| 410    | `entity_merged`          | No        | Entity merged           | "Owner own\_old123 has been merged into own\_abc123."                                                                                  | The owner/attorney was deduplicated, or a resolved entity (`ent_*`) was fused into another. The old ID is permanently redirected.      | Follow the `merged_into` field in the error response to the canonical entity. Update any cached references.                                                                                                                               |
| 422    | `entity_too_large`       | No        | Entity too large        | "This entity spans 12,431 member owners. Listing trademarks across members is limited to entities with 10,000 member owners or fewer." | An `entity_id` / `entity_group` filter, or `GET /v1/entities/{id}/trademarks`, hit a cap.                                              | Narrow the query, or query a specific member via `GET /v1/owners/{id}/trademarks`. Not retryable as-is.                                                                                                                                   |
| 429    | `rate_limited`           | Yes       | Rate limit exceeded     | "Rate limit exceeded. Retry after 30 seconds."                                                                                         | Too many requests within the rate limit window                                                                                         | Wait for `retry_after` seconds, then retry. See [Rate Limits](/api-reference/rate-limits).                                                                                                                                                |
| 500    | `internal_error`         | Yes       | Internal server error   | "An unexpected error occurred."                                                                                                        | Server-side bug or transient failure                                                                                                   | Retry with exponential backoff. If the error persists, report it via [`POST /v1/feedback`](/api-reference/administration/create-feedback) with the response's `request_id` (or email [support@signa.so](mailto:support@signa.so)).        |
| 502    | `upstream_error`         | Yes       | Bad gateway             | "The upstream service failed to respond."                                                                                              | A backend dependency or office connector returned an invalid or failed response                                                        | Retry shortly with backoff. If it persists, report it via [`POST /v1/feedback`](/api-reference/administration/create-feedback) with the response's `request_id` (or email [support@signa.so](mailto:support@signa.so)).                   |
| 503    | `service_unavailable`    | Yes       | Service unavailable     | "Database is temporarily unavailable."                                                                                                 | A backend dependency is down                                                                                                           | Wait for `retry_after` seconds, then retry. If the errors persist, report it via [`POST /v1/feedback`](/api-reference/administration/create-feedback) with the `request_id` (or email [support@signa.so](mailto:support@signa.so)).       |
| 504    | `timeout`                | Yes       | Query timeout           | "The request took too long to process. Please retry or narrow your filters."                                                           | A database query exceeded the server-side statement timeout because the filters were too broad or the result set too large             | Retry, or narrow your filters to reduce work. If it persists, report it via [`POST /v1/feedback`](/api-reference/administration/create-feedback) with the response's `request_id` (or email [support@signa.so](mailto:support@signa.so)). |
| 504    | `preview_timeout`        | No        | Preview Timeout         | "The preview could not produce a result within the server-side time budget."                                                           | A `POST /v1/watches/preview` query was too heavy to evaluate within the roughly 20 second budget                                       | Do not blind-retry (`retryable: false`). Narrow the query: add office or jurisdiction filters, reduce `trial_window_days`, or scope `filters.trademarkIds`. See [Preview Watch](/api-reference/monitoring/watches/preview).               |

`entity_too_large` carries an `error.reason` discriminator so you can branch without parsing the message: `member_owners_too_large` means the entity or group resolved to more member owners than the cap (the error also carries `member_count` and `member_count_limit`), and `family_graph_too_large` means the GLEIF family-graph walk exceeded its node or depth bound before owners were counted, which only fires on `?entity_group=` (the error carries `related_entity_limit` and `depth_limit`). Either way, narrow the query with additional filters, query a specific member directly, or use `?entity_id=` for a single entity instead of `?entity_group=`; the caps bound fan-out cost and are not retryable as-is.

***

## Handling Errors

Errors return non-2xx status codes. The response body always contains a structured `error` object and a top-level `request_id`.

```bash theme={null}
curl -s -w "\nHTTP Status: %{http_code}\n" \
  https://api.signa.so/v1/trademarks/tm_nonexistent \
  -H "Authorization: Bearer sig_YOUR_KEY"
```

The TypeScript SDK exposes typed error classes for each status code. See the [TypeScript SDK](/sdk/typescript#error-handling) reference for `instanceof` patterns and automatic retries.

### Reporting a persistent server error

Server faults (`500 internal_error`, `502 upstream_error`, `503 service_unavailable`, `504 timeout`) carry a `suggestion` that points you at the feedback channel. If retrying with backoff does not clear the error, submit it to [`POST /v1/feedback`](/api-reference/administration/create-feedback) with the response's `request_id`. Signa snapshots the matching request log onto the report, so the team can investigate exactly what you hit. Client errors (`4xx`) never carry this hint, they are fixed on your side by correcting the request. See [Submit Feedback](/api-reference/administration/create-feedback) for the full flow.

***

## Idempotency and `409 Conflict`

Mutating requests (`PATCH`, `DELETE`, and non-exempt `POST`) require an `Idempotency-Key` header (1-255 characters of `[A-Za-z0-9_-]`):

```
PATCH /v1/organization/api-keys/key_abc123
Idempotency-Key: rename-prod-key-2026-03-24-001
```

**Same key + same body** = cached response replayed (safe to retry). Replays
carry an `Idempotent-Replayed: true` response header and preserve the
original `request_id`.
**Same key + different body** = `409 conflict` error.
**Same key while the first request is still in flight** = `409
idempotency_processing`: wait for the original to finish, then retry.

Idempotency keys are scoped to your organization and expire after 24 hours.
Only successful (2xx) responses are cached, so a failed request never pins you
to its error.

```bash theme={null}
curl -s -X PATCH https://api.signa.so/v1/organization/api-keys/key_abc123 \
  -H "Authorization: Bearer sig_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-unique-key-123" \
  -d '{"name": "Production key (renamed)"}'
```

A handful of read-shaped POST endpoints are **exempt by design**: they exist
as POSTs only because their query bodies are too large or complex for a URL,
and they create nothing:

* `POST /v1/trademarks` (search)
* `POST /v1/trademarks/batch` (batch lookup)
* `POST /v1/suggest` (and other suggest endpoints)
* `POST /v1/watches/preview` (dry-run match count)
* `POST /v1/alerts/lookup` (bulk read by IDs)

On exempt endpoints the header is optional and not replayed, but if you do
send one, its **format is still validated** (1-255 characters of
`[A-Za-z0-9_-]`); a malformed value returns `400` even there.

<Note>
  `POST /v1/organization/api-keys` and `POST
      /v1/organization/api-keys/{id}/rotate` are **not** exempt. Replay is the
  correct semantic for endpoints that mint one-time secrets: retrying the
  same `Idempotency-Key` returns the **same** secret instead of minting a
  second credential.
</Note>

See [Resilience and Retries → Idempotent Requests](/guides/resilience#idempotent-requests) for the full exemption list and a worked retry example.

***

## Troubleshooting by Symptom

<AccordionGroup>
  <Accordion title="I'm getting 401 on every request">
    **Check your Authorization header format.** It must be `Authorization: Bearer sig_YOUR_KEY` (with the `Bearer` prefix). Common mistakes:

    * Missing `Bearer` prefix: `Authorization: sig_YOUR_KEY`
    * Wrong key prefix: only keys of the form `sig_{48 hex chars}` are accepted. Any other prefix is rejected with 401 before any database lookup.
    * Expired key: check `expires_at` on your API key
    * Revoked key: check the API key status in your dashboard
  </Accordion>

  <Accordion title="I'm getting 403 'insufficient scope'">
    Your API key is valid but does not have the required scope for this endpoint. Each endpoint requires specific scopes:

    * Trademark search, list, and entity GET endpoints need `trademarks:read`
    * The event feed (`/v1/events`) needs `events:read`
    * Watches, alerts, webhooks, portfolios, and saved searches need `portfolios:manage`
    * API key management needs `api-keys:manage`
    * Usage, plan, and log endpoints need `billing:read`

    Create a new key with the needed scopes or update your existing key.
  </Accordion>

  <Accordion title="I'm getting 400 'validation_error' but my request looks correct">
    Common causes:

    * **Missing required filter:** `GET /v1/trademarks` requires at least one filter (no unscoped list queries).
    * **Wrong date format:** Use ISO 8601 (`2026-03-24` or `2026-03-24T12:00:00Z`).
    * **Array syntax:** Bracketed keys (e.g. `offices[]=uspto`) are not accepted; repeated keys (e.g. `offices=uspto&offices=euipo`) are accepted as a fallback, but the canonical form is comma-separated (`offices=uspto,euipo`).
    * **Date range syntax:** Use flat underscore operators, not brackets: `?filing_date_gte=2020-01-01&filing_date_lt=2025-01-01`.
    * **Boolean values:** Only the literal strings `true` and `false` are accepted. `1`, `0`, `yes`, `no`, and `TRUE` all return a validation error.
    * **application\_number without office:** When filtering by `application_number` or `registration_number`, you must also specify `office`.
    * **Missing Idempotency-Key:** Mutating requests (`PATCH`, `DELETE`, and non-exempt `POST`s) require this header. Read-shaped POSTs (search, batch, suggest, watch preview, alert lookup) do not.
  </Accordion>

  <Accordion title="I'm getting 410 'entity_merged' for an owner I used yesterday">
    Entity resolution runs periodically. When two owner records are identified as the same entity, one is merged into the other. The `410` response includes a `merged_into` field pointing to the canonical record.

    Update your cached ID to the new one. All trademarks previously associated with the old owner are now under the canonical record.

    **Resolved entities (`ent_*`) can also 410.** When two resolved entities are fused, the loser's `ent_` id returns `410 entity_merged` with the successor in `merged_into`: follow that field to the canonical record. An entity id for an owner that has since been linked to a company profile does **not** 410; it transparently resolves to the real entity (the returned `id` may differ from the one you requested).
  </Accordion>

  <Accordion title="Search returns different results than the detail endpoint">
    This is expected behavior due to the eventual consistency model. Search results are eventually consistent (typically under 30 seconds lag after a write), while detail endpoints are immediately consistent.

    If you just updated a record, wait a few seconds and retry the search. For time-sensitive workflows, use the detail endpoint as the source of truth.
  </Accordion>

  <Accordion title="I'm hitting rate limits during bulk operations">
    For bulk lookups, use [Batch Get Trademarks](/api-reference/trademarks/batch-trademarks) (up to 100 IDs per request) instead of individual GET requests. This counts as one request against your rate limit.

    For search-heavy workloads, consider caching results on your side, using cursor pagination instead of re-executing searches, or upgrading your plan.
  </Accordion>

  <Accordion title="I'm getting 503 errors intermittently">
    This indicates a transient backend issue. The error is retryable: wait for the `retry_after` period and retry with exponential backoff. If 503 errors persist for more than a few minutes, email [support@signa.so](mailto:support@signa.so) with a `request_id` from one of the failed calls.
  </Accordion>
</AccordionGroup>
