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

> ## Agent Instructions
> Reconify's API reference is read-only for customer data. Do not invent endpoints or authentication behavior beyond the OpenAPI contract.
> The public OpenAPI document contains only the documented external /v2 contract.

# TypeScript errors and retries

> Handle failed requests, rejected events, and automatic retries.

## Two kinds of failure

A batch can fail as a whole, or one event inside it can fail on its own. These
need different handling.

| Failure                                       | How you see it                                                    |
| --------------------------------------------- | ----------------------------------------------------------------- |
| The request failed                            | The call throws `ReconifyApiError`.                               |
| The request succeeded, one event was rejected | The call resolves, and a `results` item has `status: "rejected"`. |

## Request failures

The SDK throws typed errors, so one `catch` can branch on the failure kind:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { ReconifyApiError, ReconifyTimeoutError } from "@reconifyhq/sdk";

try {
  const issue = await client.issues.getIssue({
    path: { issue_id: "00000000-0000-7000-8000-000000000099" },
  });
} catch (error) {
  if (error instanceof ReconifyApiError) {
    console.error(error.status, error.code, error.field, error.message);
  } else if (error instanceof ReconifyTimeoutError) {
    console.error("timed out after", error.timeoutMs, "ms");
  } else {
    throw error;
  }
}
```

`ReconifyApiError` exposes:

<ResponseField name="status" type="number">
  The HTTP status code.
</ResponseField>

<ResponseField name="code" type="string">
  The stable machine-readable error code, such as `not_found` or `forbidden`.
  When the response carries no code, the SDK derives one from the status:
  `not_found`, `validation_error`, `rate_limited`, `service_unavailable`, or
  `api_error`.
</ResponseField>

<ResponseField name="field" type="string | undefined">
  The failing request field, when the API identified one.
</ResponseField>

<ResponseField name="details" type="ReconifyErrorDetail[]">
  Every validation entry the API returned, each with `message` and an optional
  `field`.
</ResponseField>

<ResponseField name="body" type="unknown">
  The parsed response body.
</ResponseField>

<ResponseField name="response" type="Response">
  The raw `Response`. Read `response.headers.get("x-request-id")` and include it
  in support requests.
</ResponseField>

`ReconifyTimeoutError` carries `code: "timeout"` and `timeoutMs`. Cancelling
through your own `AbortSignal` rejects with the signal's reason instead.

### Which status to act on

Each status implies one action:

| Status       | Do this                                                                              |
| ------------ | ------------------------------------------------------------------------------------ |
| `400`, `422` | Fix the request. Do not retry it unchanged.                                          |
| `401`        | Check the key value and that it is still active.                                     |
| `403`        | Use a key with the required scope. `write` for `POST` and `PATCH`, `read` for `GET`. |
| `404`        | Confirm the identifier belongs to your organization.                                 |
| `409`        | An `Idempotency-Key` was reused with a different note body.                          |
| `503`        | Retry with backoff. The SDK already does this.                                       |

## Rejected events inside a batch

The batch endpoint returns `202` when at least one event is accepted or
duplicated, and `422` when every event is rejected. Always read `results`.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const result = await client.ingestion.ingestMonitoringEvents({ body: batch });

for (const item of result.results) {
  if (item.status === "rejected") {
    logger.error("event rejected", {
      index: item.index,
      code: item.code,
      field: item.field,
      message: item.message,
    });
  }
}
```

`item.index` matches the position in the array you sent. Use it to locate the
producer-side record without writing the event or customer reference to logs.

| `code`                 | Meaning                                                                    |
| ---------------------- | -------------------------------------------------------------------------- |
| `invalid_event`        | A field failed validation. Read `field`.                                   |
| `unknown_field`        | The event carried a field the contract does not define.                    |
| `idempotency_conflict` | The `id` was reused with different content. Investigate rather than retry. |
| `duplicate`            | Reconify already stored this event. Treat it as delivered.                 |
| `malformed_request`    | The request body could not be parsed.                                      |

## Automatic retries

The client retries on its own, with exponential backoff.

| Setting              | Default                          |
| -------------------- | -------------------------------- |
| `maxAttempts`        | `3`, including the first attempt |
| `baseDelayMs`        | `250`                            |
| `maxDelayMs`         | `5000`                           |
| `retryNonIdempotent` | `false`                          |

Retries apply to `429` and `503` responses, request timeouts, and transport
failures. A `Retry-After` header overrides the computed backoff, capped at
`maxDelayMs`.

By default only `GET`, `HEAD`, and `OPTIONS` are retried, so event ingestion is
**not** retried automatically.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const client = new ReconifyClient({
  apiKey: process.env.RECONIFY_API_KEY,
  timeoutMs: 30_000,
  retry: {
    maxAttempts: 3,
    baseDelayMs: 250,
    maxDelayMs: 5_000,
  },
});
```

Turn on `retryNonIdempotent` only when every event in the batch has a stable
`id`. Reconify then returns `duplicate` for events it already stored instead of
recording them twice.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const result = await client.ingestion.ingestMonitoringEvents({
  body: batch,
  request: { retry: { retryNonIdempotent: true } },
});
```

<Warning>
  An event without `id` receives a generated ID on every attempt. Retrying a batch
  that contains such events records them more than once.
</Warning>

## Next

Put this together in
[Send events with TypeScript](/sdks/typescript/send-events).
