# CLI

> Reference for @apitraffic/cli: pull and check pinned offline definitions in CI, with authentication, flags, exit codes, JSON output and troubleshooting.

Source: https://www.apitraffic.io/docs/reference/cli

`@apitraffic/cli` keeps the [offline definitions](https://www.apitraffic.io/docs/reference/offline-definitions) committed to your
repository in step with the configuration in your ApiTraffic account.

```bash
npx @apitraffic/cli definitions pull    # write .apitraffic/definitions/sdk.json
npx @apitraffic/cli definitions check   # fail when the committed file has drifted
```

> **Info:** **No install step and no dependencies.** The package has zero runtime dependencies, so `npx` fetches and
> runs it in about a second - which is the point, because its main home is a CI job. Node 20 or newer.

## Install

You do not need to. `npx` is the intended way to run it, in CI and locally alike.

If you would rather pin a version - sensible for a CI gate, so a release cannot change your build's
behavior without a commit:

**npx (recommended)**

```bash
npx @apitraffic/cli@0.2 definitions check
```

**npm**

```bash
npm install --save-dev @apitraffic/cli
```

```json
{
  "scripts": {
    "definitions:pull": "apitraffic definitions pull",
    "definitions:check": "apitraffic definitions check"
  }
}
```

**pnpm**

```bash
pnpm add -D @apitraffic/cli
```

## Authentication

The CLI uses the **same ingestion key your SDK already has**. No new credential, no new permission, no
extra setup - the snapshot endpoint carries the same permission as the settings endpoint the SDK calls on
every boot.

| Variable | Meaning | Required |
|---|---|---|
| `API_TRAFFIC_TOKEN` | Your ingestion key. `APITRAFFIC_TOKEN` is read too. | yes, or `--token-stdin` |
| `API_TRAFFIC_BUCKET` | The bucket ID, e.g. `k3m9p2x` | yes |
| `API_TRAFFIC_API_HOST` | Defaults to `api.apitraffic.io` | no |
| `API_TRAFFIC_DEFINITIONS_PATH` | Overrides where the file lives | no |

```bash
export API_TRAFFIC_TOKEN="your-ingestion-key"
export API_TRAFFIC_BUCKET="k3m9p2x"
```

For a one-off run, pass them as flags instead. This is the form the **Offline Definitions** dialog on the
**API Tokens** page gives you (choose **Definitions** for an environment), with your bucket already filled in:

```bash
npx @apitraffic/cli definitions pull -b k3m9p2x -t YOUR_API_TOKEN
```

> **Warning:** **Use the flag for a one-off, the environment variable in CI.** A token passed as an argument lands in
> your shell history, in `ps` output, and in the logs of any CI runner that echoes the commands it runs -
> which is fine for something you type once at your own terminal, and not fine for a pipeline.

## `definitions pull`

Fetches your current configuration and writes it where the SDK will look for it.

```bash
npx @apitraffic/cli definitions pull
```

```text
✔ wrote .apitraffic/definitions/sdk.json

! This file contains your WAF policy and redaction rules.
  Both describe what you block and what you consider sensitive. Commit it to a
  private repository; do not publish it.

  bucket     k3m9p2x
  hash       679c2b8ce73c
  generated  2026-08-14T12:00:00.000Z (today)
  rules      12 redactions, 3 exclusions
  waf        block

  Commit the change so your next cold start ships these rules.
```

Then commit it:

```bash
git add .apitraffic/definitions/sdk.json
git commit -m "chore: refresh ApiTraffic definitions"
```

### What it will and will not do

- **Rewrites an existing file wherever it already lives**, walking up from the current directory. It will
  not create a second file lower down that the first would shadow - a "refresh" that silently changed
  nothing would be the worst possible outcome for a fallback.
- **Refuses to overwrite a file pinned to a different bucket.** Silently replacing one service's redaction
  rules with another's is a data-protection incident dressed up as a convenience. Use `--force` to
  override, or `--bucket-qualified` to keep both.
- **Writes nothing if the API answers with something unusable.** A bad response must not replace a file
  that currently works.
- **Writes atomically**, via a temporary file and a rename, so an interrupted pull cannot leave truncated
  JSON for the next cold start to read. The temporary file has an unpredictable name, is created only if
  nothing is already there, and is removed if any step fails. A new file is `0644`; a rewrite keeps the
  permissions the existing file had.
- **Stays inside your repository.** The walk up from the working directory stops at the repository root
  (the first directory containing `.git`), so a stray file above your checkout is never rewritten.
- **Formats for review**: pretty-printed, with `config` key-sorted, so your commit diff shows which rule
  changed rather than one reflowed line.

## `definitions check`

Compares the committed file against your live configuration. This is the command that makes the whole
feature work, because it puts staleness in front of the person doing the deploy, at the moment of the
deploy.

```bash
npx @apitraffic/cli definitions check
```

**In sync**

```text
✔ .apitraffic/definitions/sdk.json is current

  bucket     k3m9p2x
  hash       679c2b8ce73c
  generated  2026-08-14T12:00:00.000Z (today)
```

**Drifted**

```text
✖ .apitraffic/definitions/sdk.json is out of date

  pinned     0b4187423da1  generated 74 days ago
  current    679c2b8ce73c

  Your running servers are unaffected - they hold live config.
  This is about the next cold start that cannot reach ApiTraffic:
  it would apply these rules instead of the current ones.

  Refresh and commit:  npx @apitraffic/cli definitions pull
```

**Edited by hand**

```text
✖ .apitraffic/definitions/sdk.json has been edited by hand

  states     679c2b8ce73c
  contains   0b4187423da1  (matches your live config)

  The config is right, but the file's own configHash disagrees with it.
  Regenerate it so the two match:  npx @apitraffic/cli definitions pull
```

### Exit codes

| Code | Meaning | What it usually means |
|---|---|---|
| `0` | In sync | Nothing to do |
| `1` | Drifted, edited by hand, missing, or pinned to another bucket | Run `definitions pull` and commit |
| `2` | Could not determine | Bad token, network failure, or a bad response |

> **Note:** **`1` and `2` are deliberately different.** Collapsing them into "non-zero" would mean an ApiTraffic blip
> breaks your build - which is the exact failure this feature exists to remove, reintroduced inside your
> pipeline. Choose which one you want to be strict about with the flags below.

> **Warning:** **By default, exit code `2` fails your CI build whenever ApiTraffic is unreachable.** In a pipeline, run
> `definitions check --ignore-unreachable`: drift and a rejected token still fail the build, but our outage
> does not block your deploy. See [If ApiTraffic is unreachable](https://www.apitraffic.io/docs/reference/unreachable#your-ci-pipeline).

```bash
npx @apitraffic/cli definitions check --ignore-unreachable
```

### Local checks

A `check` in your local checkout answers a slightly different question from a CI one: it tells you the
account moved on since you last pulled, but blocking your commit on somebody else's dashboard edit is
usually the wrong trade.

```bash
npx @apitraffic/cli definitions check --warn-only --quiet || true
```

## Options

| Flag | Applies to | |
|---|---|---|
| `--bucket`, `-b ` | both | Override `API_TRAFFIC_BUCKET` |
| `--token-stdin` | both | Read the token from standard input (one line, surrounding whitespace trimmed), e.g. `printf %s "$TOKEN" \| apitraffic definitions pull --token-stdin`. Run in a terminal, it prompts on stderr and does not echo what you type |
| `--token`, `-t ` | both | Override `API_TRAFFIC_TOKEN`. Prints a warning: an argument is visible in `ps`, shell history and CI logs - prefer the env var (`API_TRAFFIC_TOKEN`, or `APITRAFFIC_TOKEN`) or `--token-stdin` |
| `--api-host ` | both | Override `API_TRAFFIC_API_HOST` |
| `--insecure` | both | Allow an `http://` API host that is not `localhost` (sends the token unencrypted) |
| `--out`, `-o ` | both | The file to write or check |
| `--redact-at ` | both | `client` (default) or `server` |
| `--timeout ` | both | Per attempt, default `15000` |
| `--json` | both | Machine-readable output on stdout |
| `--quiet`, `-q` | both | Suppress the human-readable output |
| `--bucket-qualified` | `pull` | Write `sdk.{bucketSid}.json` |
| `--force` | `pull` | Overwrite a file pinned to a different bucket |
| `--warn-only` | `check` | Report drift, still exit `0` |
| `--ignore-unreachable` | `check` | Exit `0` when ApiTraffic cannot be reached |
| `--help`, `-h` | | |
| `--version`, `-v` | | |

## JSON output

`--json` prints a single object on stdout and nothing else, so a script can act on the result. Errors
still go to stderr - a CI log showing only `{"ok":false}` costs somebody twenty minutes.

**check (drifted)**

```json
{
  "ok": false,
  "command": "check",
  "status": "stale",
  "filePath": "/repo/.apitraffic/definitions/sdk.json",
  "configHash": "sha256:0b4187423da1...",
  "remoteConfigHash": "sha256:679c2b8ce73c...",
  "generatedAt": "2026-06-01T09:12:44.000Z",
  "ageDays": 74,
  "handEdited": false
}
```

`status` is one of `current`, `stale`, `modified`, `missing`, `unreadable`, `bucket-mismatch` or
`undetermined`.

**pull**

```json
{
  "ok": true,
  "command": "pull",
  "filePath": "/repo/.apitraffic/definitions/sdk.json",
  "created": false,
  "changed": true,
  "bucketSid": "k3m9p2x",
  "configHash": "sha256:679c2b8ce73c...",
  "previousConfigHash": "sha256:0b4187423da1...",
  "generatedAt": "2026-08-14T12:00:00.000Z"
}
```

## Where the file lives

The CLI and the SDK resolve the path identically - if they disagreed, every command would report success
while the SDK read a different file.

1. `API_TRAFFIC_DEFINITIONS_PATH`, if set. It is an **override**, not a preference: nothing else is
   checked.
2. `.apitraffic/definitions/sdk.{bucketSid}.json`
3. `.apitraffic/definitions/sdk.json`

Both walk up from the working directory, so starting your process from a subdirectory of the repository
still finds the file at the root. The CLI stops at the repository root - the first directory containing
`.git` - because it manages the file your repository commits; inside the repository it sees exactly what
the SDK sees, in the same order.

### Several buckets in one repository

```bash
API_TRAFFIC_BUCKET=p4n8w2k npx @apitraffic/cli definitions pull --bucket-qualified
API_TRAFFIC_BUCKET=s7q3m9d npx @apitraffic/cli definitions pull --bucket-qualified
```

Each SDK prefers the file matching its own bucket and falls back to a shared `sdk.json`. A file whose
`bucketSid` does not match is refused rather than best-guessed.

## Reliability

The CLI is built on the assumption that it runs in a pipeline that must not break because of us.

- **Transient failures are retried** - three attempts with backoff - before anything is reported. A one
  second hiccup does not fail your build.
- **Authentication failures are not retried.** Repeating a rejected credential only slows down a failure
  whose cause will not change.
- `--ignore-unreachable` converts "we could not be reached" into a pass, while still failing on real
  drift and on a rejected token.

## Troubleshooting

**Missing API_TRAFFIC_TOKEN and API_TRAFFIC_BUCKET**

Both missing values are reported together, on purpose - finding out about the second only after fixing
the first costs a whole CI round trip.

The token is your **ingestion key**, the same one your SDK uses. Create or manage it on the **API Tokens** page in your account settings; see [API tokens](https://www.apitraffic.io/docs/account/api-tokens).

**The API rejected the token (HTTP 401/403)**

The token is invalid, or it does not grant `at:DescribeSdkSettings` on this bucket. An ingestion key
scoped to a different environment will produce this against a bucket it cannot see.

**Bucket ... was not found**

The bucket sid is wrong, or the token cannot see it. Check `API_TRAFFIC_BUCKET` against the sid on the
bucket's settings page.

**check says the file is missing, but it is right there**

The CLI looked where the *SDK* looks. Two things usually explain it: `API_TRAFFIC_DEFINITIONS_PATH` is
set in one environment but not the other, or the file is bucket-qualified for a different bucket than
`API_TRAFFIC_BUCKET` names.

Run with `--json` - `filePath` tells you exactly which file it read.

**check keeps reporting 'edited by hand'**

The file's own `configHash` field disagrees with a hash recomputed from its `config`. Something edited
the file after it was pulled - a formatter, a merge resolution, or a person.

It matters because the SDK reports the **recomputed** hash, so the Servers page would show drift that
the file's own contents cannot explain. `definitions pull` regenerates both consistently.

**Refusing to send the API token over plain http://**

The API host is `http://` and not this machine. The token is your ingestion key, and over plain HTTP
anyone on the network path can read it from the first request. Use `https://`, or a bare host name
(which means `https://`).

`http://localhost`, `http://127.0.0.1` and `http://[::1]` are allowed without a flag, for local
development. For any other host (a compose service such as `http://api:8081`, an internal staging box),
pass `--insecure` if you trust that network.

**'...' is not a bucket sid**

`API_TRAFFIC_BUCKET` (or `--bucket`) holds something other than a bucket sid: a sid is letters, digits,
`_` and `-` only, e.g. `k3m9p2x`. The value becomes part of the file name `sdk.{bucketSid}.json`, so
anything else - a path separator, `..`, a stray space or newline from a copied secret - is refused.

**--token needs a value**

The argument after `--token` (or `-t`) was another flag. A token that genuinely starts with `-` is
accepted as-is unless it is also one of this tool's flags; `--token=` always works.

**Could not write the file**

A filesystem problem rather than an API one: a read-only checkout, or a directory the CI user cannot
create. The CLI never partially writes - the existing file is untouched.

## Related

- [Offline definitions](https://www.apitraffic.io/docs/reference/offline-definitions):
    What the file is for and how the fallback works.

- [If ApiTraffic is unreachable](https://www.apitraffic.io/docs/reference/unreachable):
    What the SDK and this CLI do during an outage.

- [Buckets API](https://www.apitraffic.io/docs/api-reference/buckets):
    The endpoint this CLI wraps.

- [Servers](https://www.apitraffic.io/docs/integrations/servers):
    See which servers carry a stale definitions file.
