# Non-retriable errors

> Stop retries when another attempt cannot fix the error.

Throw `NonRetriableError` when the same work will fail again, such as an invalid ID or a record that will not become available. Inngest skips the remaining retries for the step or function where you throw it. Use a standard `Error` for a timeout or another temporary failure so Inngest can retry.

## Stop retries for a step

This TypeScript v4 example uses the `inngest` client from the [Quick start](/docs-markdown/durable-execution/quick-start). Replace the URL with your service endpoint.

```typescript {{ title: "TypeScript" }}
import { NonRetriableError } from "inngest";

export const loadOrder = inngest.createFunction(
  {
    id: "load-order",
    triggers: { event: "shop/order.requested" },
  },
  async ({ event, step }) => {
    return await step.run("load-order", async () => {
      const response = await fetch(
        `https://api.example.com/orders/${event.data.orderId}`
      );

      if (response.status === 404) {
        throw new NonRetriableError("order does not exist");
      }
      if (!response.ok) {
        throw new Error(`order API returned ${response.status}`);
      }

      return response.json();
    });
  }
);
```

```python {{ title: "Python" }}
import httpx
import inngest

@inngest_client.create_function(
    fn_id="load-order",
    trigger=inngest.TriggerEvent(event="shop/order.requested"),
)
async def load_order(ctx: inngest.Context) -> inngest.JSON:
    async def load() -> inngest.JSON:
        async with httpx.AsyncClient() as http:
            response = await http.get(
                f"https://api.example.com/orders/{ctx.event.data['orderId']}"
            )

        if response.status_code == 404:
            raise inngest.NonRetriableError("order does not exist")
        if not response.is_success:
            raise Exception(f"order API returned {response.status_code}")

        return response.json()

    return await ctx.step.run("load-order", load)
```

```go {{ title: "Go" }}
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"github.com/inngest/inngestgo"
	"github.com/inngest/inngestgo/step"
)

type OrderRequestedData struct {
	OrderID string `json:"orderId"`
}

func LoadOrder(client inngestgo.Client) (inngestgo.ServableFunction, error) {
	return inngestgo.CreateFunction(
		client,
		inngestgo.FunctionOpts{ID: "load-order"},
		inngestgo.EventTrigger("shop/order.requested", nil),
		func(ctx context.Context, input inngestgo.Input[OrderRequestedData]) (any, error) {
			return step.Run(ctx, "load-order", func(ctx context.Context) (map[string]any, error) {
				resp, err := http.Get(fmt.Sprintf("https://api.example.com/orders/%s", input.Event.Data.OrderID))
				if err != nil {
					return nil, err
				}
				defer resp.Body.Close()

				if resp.StatusCode == http.StatusNotFound {
					return nil, inngestgo.NoRetryError(errors.New("order does not exist"))
				}
				if resp.StatusCode >= 300 {
					return nil, fmt.Errorf("order API returned %d", resp.StatusCode)
				}

				var order map[string]any
				err = json.NewDecoder(resp.Body).Decode(&order)
				return order, err
			})
		},
	)
}
```

If the API returns `404`, Inngest marks `load-order` failed without trying that step again. Other errors follow the configured [retry policy](/docs-markdown/durable-execution/guides-and-advanced/error-handling/retries). Use `404` this way only when the order cannot appear later.

## Decide whether the function fails

When a `step.run()` fails, it throws a `StepError` into the function, even if its handler threw `NonRetriableError`. The caught value is not the original `NonRetriableError` instance. Catch the failed step to run a fallback or compensation in another step. If you leave it uncaught, the function fails. A configured [failure handler](/docs-markdown/durable-execution/guides-and-advanced/error-handling/failure-handlers) then handles that final failure.

Throwing `NonRetriableError` outside a step stops retries for the function itself. Use this for validation before any step runs:

```typescript {{ title: "TypeScript" }}
if (!event.data.orderId) {
  throw new NonRetriableError("orderId is required");
}
```

```python {{ title: "Python" }}
if not ctx.event.data.get("orderId"):
    raise inngest.NonRetriableError("orderId is required")
```

```go {{ title: "Go" }}
if input.Event.Data.OrderID == "" {
	return nil, inngestgo.NoRetryError(errors.New("orderId is required"))
}
```

Do not catch a failed step and throw a standard `Error` unless you want the function code to retry. For a recovery example, see [Rollbacks](/docs-markdown/durable-execution/guides-and-advanced/error-handling/rollbacks).

## Keep the original cause

When you convert an error from your app, pass it as `cause` so the failure retains diagnostic context:

```typescript {{ title: "TypeScript" }}
throw new NonRetriableError("orderId is invalid", { cause: error });
```

```python {{ title: "Python" }}
# `from error` keeps the original exception as __cause__.
raise inngest.NonRetriableError("orderId is invalid") from error
```

```go {{ title: "Go" }}
// Wrap with %w so errors.Is and errors.As still see the original error.
return nil, inngestgo.NoRetryError(fmt.Errorf("orderId is invalid: %w", err))
```

The TypeScript v4 constructor takes a message and an optional `{ cause }` object. Choose this error only after you know another attempt cannot succeed. It does not undo external calls that already completed.