> ## Documentation Index
> Fetch the complete documentation index at: https://restate-6d46e1dc-create-pull-request-patch.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Stop infinite retries with Terminal Errors.

Restate handles retries for failed invocations.

<Info>
  Check out the [Error Handling guide](/guides/error-handling) to learn more about how Restate handles transient errors, terminal errors, retries, and timeouts.
</Info>

## Retry strategies

By default, Restate does infinite retries with an exponential backoff strategy.

Check out the [error handling guide](/guides/error-handling) to learn how to customize this.

## Terminal errors

For failures for which you do not want retries, but instead want the invocation to end and the error message
to be propagated back to the caller, you can throw a **terminal error**.

You can throw a `TerminalError` with an HTTP status code and structured metadata anywhere in your handler:

```typescript {"CODE_LOAD::ts/src/develop/error_handling.ts#terminal"}  theme={null}
throw new TerminalError("Payment declined.", {
  errorCode: 402,
  metadata: {
    reason: "insufficient_funds",
    paymentId: "payment-123",
  },
});
```

The message, code, and metadata are recorded with the failure and propagated to callers. Catch `TerminalError` to inspect them and build your control flow around the failure:

```typescript {"CODE_LOAD::ts/src/develop/error_handling.ts#terminal_metadata_caller"}  theme={null}
async function checkout(ctx: restate.Context, paymentId: string) {
  try {
    await ctx.serviceClient(billingService).charge(paymentId);
    return { status: "charged" };
  } catch (error) {
    if (error instanceof TerminalError) {
      return {
        status: "declined",
        code: error.code,
        reason: error.metadata?.reason,
      };
    }
    throw error;
  }
}
```

<Note>
  Terminal error metadata requires Restate Server 1.6 or newer. Metadata keys and values must be strings.
</Note>

<Info>
  When you throw a terminal error, you might need to undo the actions you did earlier in your handler to make sure that your system remains in a consistent state.
  Have a look at our [sagas guide](/guides/sagas) to learn more.
</Info>

## Retryable errors with custom delay

Use `RetryableError` to signal that Restate should retry with a specific delay.
This is useful when interacting with external APIs that return a `Retry-After` header.

`RetryableError` is primarily designed for use inside `ctx.run` blocks:

```typescript {"CODE_LOAD::ts/src/develop/error_handling.ts#retryable"}  theme={null}
await ctx.run("call API", async () => {
    const res = await fetch("https://api.example.com/data");
    if (!res.ok) {
        const retryAfter = res.headers.get("Retry-After");
        throw new RetryableError("Rate limited", {
            retryAfter: { seconds: Number(retryAfter ?? 30) },
        });
    }
    return res.json();
}, { maxRetryAttempts: 10 });
```

You can also wrap an existing error using the `RetryableError.from` helper:

```typescript {"CODE_LOAD::ts/src/develop/error_handling.ts#retryable_from"}  theme={null}
throw RetryableError.from(cause, {
    retryAfter: { seconds: 30 },
});
```

<Note>
  Unlike `TerminalError` which stops retries permanently, `RetryableError` tells Restate to retry after the specified delay. You can combine it with `maxRetryAttempts` and `maxRetryDuration` in the run options.
</Note>

## Pausing invocations

<Note title="Preview feature">
  `PauseError` and `onJournalMismatchErrors` require Restate Server 1.7 with `RESTATE_EXPERIMENTAL_ENABLE_PROTOCOL_V7=true` enabled.
</Note>

A paused invocation stops retrying until you [resume it](/services/invocation/managing-invocations#resume). This is useful when an operator must fix the underlying problem before another attempt can succeed.

### Pause from a durable step

Throw `PauseError` from a `ctx.run` closure to pause the invocation. If you throw it outside `ctx.run`, Restate ignores the pause request and applies the normal invocation retry policy.

```typescript {"CODE_LOAD::ts/src/develop/error_handling.ts#pause_error"}  theme={null}
async function chargeCard(ctx: restate.Context, paymentId: string) {
  return ctx.run("charge card", async () => {
    const providerUrl = process.env.PAYMENT_PROVIDER_URL;
    if (providerUrl === undefined) {
      throw new restate.PauseError("Payment provider is not configured");
    }

    const response = await fetch(`${providerUrl}/payments/${paymentId}`, {
      method: "POST",
    });
    if (!response.ok) {
      throw new Error(`Payment provider returned HTTP ${response.status}`);
    }
    return response.json();
  });
}
```

After you fix the configuration and resume the invocation, Restate retries the `ctx.run` closure.

### Handle journal mismatch errors

A [journal mismatch](/services/versioning#journal-mismatch-errors) happens when replayed code produces different Restate operations from the original execution. By default, Restate applies the invocation retry policy. Set `onJournalMismatchErrors` on a service or handler to choose another outcome.

```typescript {"CODE_LOAD::ts/src/develop/error_handling.ts#journal_mismatch"}  theme={null}
const paymentService = restate.service({
  name: "PaymentService",
  handlers: {
    charge: async (_ctx: restate.Context, paymentId: string) => {
      return `Charged ${paymentId}`;
    },
  },
  options: {
    onJournalMismatchErrors: "pause",
  },
});
```

| Value     | Behavior                                                       |
| --------- | -------------------------------------------------------------- |
| `"retry"` | Apply the normal invocation retry policy. This is the default. |
| `"pause"` | Pause the invocation for inspection and manual recovery.       |
| `"fail"`  | End the invocation with a terminal failure.                    |

## Mapping errors to `TerminalError`

If you're using external libraries (e.g., for validation), you might want to automatically convert certain error types into terminal errors.

You can do this using the `asTerminalError` option in your [service configuration](/services/configuration).

For example, to fail with `TerminalError` for each `MyValidationError`, do the following:

```typescript {"CODE_LOAD::ts/src/develop/error_handling.ts#as_terminal"}  theme={null}
class MyValidationError extends Error {}

const greeter = restate.service({
  name: "greeter",
  handlers: {
    greet: async (ctx: restate.Context, name: string) => {
      if (name.length === 0) {
        throw new MyValidationError("Length too short");
      }
      return `Hello ${name}`;
    },
  },
  options: {
    asTerminalError: (err) => {
      if (err instanceof MyValidationError) {
        // My validation error is terminal
        return new restate.TerminalError(err.message, { errorCode: 400 });
      }
    },
  },
});
```
