---
title: "Drift detection"
description: "How apiweld check finds upstream changes to the endpoints you use, and how it decides whether they're breaking, risky, or safe."
---

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

# Drift detection

APIs change. Providers rename fields, add required parameters, and retire endpoints, and most don't tell you which of your calls are affected. `apiweld check` compares the provider's current spec with the version in your [lockfile](/config#lockfile) and reports only changes to the endpoints you use.

Run it whenever you like: by hand, in a pre-commit hook, on a schedule in your CI, or from your agent with `check_drift`. Apiweld doesn't run anything on a server, so you decide when checks happen.

```sh
npx apiweld check
```

## What gets reported

Only changes that affect your selected endpoints. If Stripe adds a field to an endpoint you don't call, `check` stays quiet. If Stripe changes a schema that one of your endpoints uses, that change is reported against every selected endpoint that uses the schema.

Each change gets one of three labels:

| Label | What it means | Examples | What Apiweld does |
| --- | --- | --- | --- |
| **Breaking** | Code that works today will fail. | An endpoint was removed. A request field became required. A response field was removed or changed type. An allowed request value was removed. | Writes a [heal plan](/heal). Never regenerates on its own. |
| **Risky** | Might break you, depending on how you use it. | A new value appeared in a response enum. A field became nullable. The auth scheme changed. The endpoint was deprecated. | Regenerates in a separate worktree, type-checks your project, and reports the result. |
| **Safe** | Nothing you use can break. | A new optional field or parameter. A description changed. | Regenerates automatically if the API's `policy.autoRegenerate` is `"safe"`. |

The labels come from [oasdiff](https://www.oasdiff.com), which has a rule for each kind of change. Its `ERR` level becomes breaking, `WARN` becomes risky, and `INFO` becomes safe.

If a provider's conventions make one rule fire on changes that don't matter to you, re-label that rule for that API with [`levelOverrides`](/config#per-api-options). The override lives in your config and applies to that API only.

## How a check runs

1. **Fetch only if something changed**

   Apiweld requests the spec with the ETag from the last fetch. If the provider says nothing changed, or the new spec is byte-for-byte the same, the check ends here.
2. **Normalize and slice**

   The new spec is normalized (references resolved, keys sorted, Swagger 2.0 upgraded to OpenAPI 3) and cut down to your endpoints and the schemas they use. That's the slice. If one of your endpoints no longer exists, that's recorded as a breaking change.
3. **Skip changes you don't use**

   If the spec changed but your slice didn't, the change was somewhere you don't use. The lock is updated and nothing is reported.
4. **Compare and label**

   oasdiff compares the old and new specs. Apiweld keeps only the findings for your endpoints and labels each one.
5. **Report**

   Results print as a table, or as JSON with `--json`. A Markdown report is also written to `.apiweld/reports/`.

## Use it as a CI gate

`check` exits with a non-zero code when it finds a breaking change. To also fail on risky changes, use `--fail-on risky`.

```sh
npx apiweld check --fail-on risky
```

To have CI open a pull request with the fix, see [Heal in CI](/heal#in-ci).

## Apply safe updates

`apiweld update` runs a check and then applies what your policy allows. Safe changes regenerate in place when `policy.autoRegenerate` is `"safe"`. Risky changes are reported with their type-check results. Breaking changes stop at the heal plan.

## Runtime drift

Specs aren't always accurate. Sometimes the live API returns something the spec doesn't describe. With `validators: "zod"`, the generated client includes a `reportingFetch` that checks each response against the spec's schema without blocking anything:

```ts
import { client } from "./apis/stripe/client.gen";
import { reportingFetch } from "./apis/stripe";

client.setConfig({ fetch: reportingFetch });
```

When a response doesn't match, one line is appended to `.apiweld/drift.log.jsonl` with the endpoint, the JSON path, the expected type, the received type, and a timestamp. Response values are never logged. The next `apiweld check` includes these entries in its report, next to the spec changes.

Source: https://apiweld.dev/drift/index.mdx
