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

# Rulebase CLI

> Push, read, and reconcile work items from a terminal, an agent, or a CI job with @rulebase/cli: JSON in, JSON out, no interactive prompts.

`@rulebase/cli` is the Rulebase command line. Beyond the credential helpers
(`doctor`, `login`, `whoami`), it exposes the whole work-item API as
non-interactive commands that read JSON and print JSON. That makes it the
shortest path from a script, a coding agent, or a CI job to the
[`/v1/work_items`](/api-v2-reference/create-work-item) endpoints, without
writing an HTTP client or handling regional hosts yourself.

## Install and run

The CLI is a zero-dependency Node package. Node 20 or newer is required.

```bash theme={null}
# Install once, then call it by name
npm install -g @rulebase/cli
rulebase work-items list --limit 5
```

The package installs a single `rulebase` binary. Prefer the global install on a
workstation or an agent image: the command is short, and there is no npm
registry round trip on every invocation.

In CI, add it as a dev dependency instead, so the version is recorded in your
lockfile rather than resolved at job time:

```bash theme={null}
npm install --save-dev @rulebase/cli
npx rulebase work-items list
```

Every example below assumes `rulebase` is on your `PATH`. If you would rather
not install anything, prefixing any command with `npx @rulebase/cli` works the
same way.

`rulebase --help` lists every command and the exit codes in one screen.

## Authenticate

Work-item commands authenticate with a **Rulebase API key**, the same key you
would put in an `Authorization: Bearer` header. Create one in
[Settings > Connections](https://app.rulebase.co/settings/connections); see
[Set up API access](/guides/api-access) for the details.

<Note>
  The OAuth login (`rulebase login --region us|eu`) exists for MCP tool calls only.
  REST work-item commands never use it. If `whoami` shows you are signed in with
  OAuth but no API key is configured, the work-item commands will still fail with
  a credentials error.
</Note>

Two ways to supply the key:

```bash theme={null}
# 1. Environment variable — best for CI
export RULEBASE_API_KEY=rk_live_...
rulebase work-items list

# 2. Stored credential — best for a workstation
export RULEBASE_API_KEY=rk_live_...
rulebase login --auth api-key   # verifies the key, then stores it
unset RULEBASE_API_KEY
```

Without `RULEBASE_API_KEY`, `login --auth api-key` prompts for the key with the
input hidden. **The CLI never accepts an API key as a command-line argument**,
because argv shows up in shell history, `ps` output, and CI transcripts.

### Where credentials live, and which one wins

Stored credentials go in `~/.rulebase/credentials.json`, in a directory created
with mode `0700` and a file written with mode `0600`. Override the location with
`RULEBASE_CREDENTIALS_PATH`.

Precedence, highest first:

1. `RULEBASE_API_KEY` from the environment.
2. The stored key for the region you passed with `--region`.
3. The stored key, when only one region has one.

If keys are stored for both regions and you do not pass `--region`, the CLI
stops rather than guessing, and tells you to pick one.

### US and EU

Rulebase runs separate US and EU deployments with separate key stores, and a key
sent to the wrong region returns exactly the same `401 Unauthorized` as a
revoked key. The CLI resolves the region for you: it uses `--region` (or
`RULEBASE_REGION`) when given, the region the key is stored under otherwise,
and probes both regions when it has neither.

```bash theme={null}
rulebase work-items list --region eu
RULEBASE_REGION=eu rulebase work-items list
```

## Output contract

Every work-item command writes exactly one JSON object to stdout and nothing
else. Progress and warnings go to stderr, never secrets.

```json theme={null}
{ "ok": true, "data": { "id": "0197ac58-70c9-7bc0-9ba8-1bb7ce8f78cc" } }
```

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "unauthorized",
    "message": "The API key was rejected (401).",
    "details": { "region": "us" }
  }
}
```

Exit codes let a script branch without parsing the body:

| Code | Meaning                                               |
| ---- | ----------------------------------------------------- |
| `0`  | Success                                               |
| `1`  | A batch apply completed with at least one failed item |
| `2`  | Usage, malformed JSON, or schema validation error     |
| `3`  | Missing, ambiguous, or wrong-region credentials       |
| `4`  | Network failure or an API error response              |

## Commands

All commands take `--region us\|eu` and read input from a file path or `-` for
stdin.

### `work-items create`

Creates a work item from a JSON object. Create-only: a duplicate `external_id`
returns exit code `4` with `error.code` `conflict`.

```bash theme={null}
cat > item.json <<'JSON'
{
  "external_id": "TASK-9182",
  "agent_email": "alex@example.com",
  "type": "fraud_review",
  "status": "completed",
  "completed_at": "2026-05-12T14:32:00Z",
  "custom_attributes": { "risk_score": 78 }
}
JSON

rulebase work-items create --file item.json
```

### `work-items get`

By Rulebase ID or by your own identifier:

```bash theme={null}
rulebase work-items get 0197ac58-70c9-7bc0-9ba8-1bb7ce8f78cc
rulebase work-items get --external-id TASK-9182
```

### `work-items list`

Newest completion first, cursor paginated:

```bash theme={null}
rulebase work-items list --limit 50 --status completed
rulebase work-items list --cursor "$CURSOR"
rulebase work-items list --status pending --status in_progress
rulebase work-items list --completed-after 2026-05-01T00:00:00Z
rulebase work-items list --query TASK-91
rulebase work-items list --all          # follows every page
```

`meta.page.next` carries the cursor for the next page and is `null` on the last
one. With `--all` the CLI walks the pages itself and returns one combined array.

### `work-items update`

Patches a stored work item. Omitted fields keep their values,
`custom_attributes` merge with what is stored, and `external_id` cannot change.

```bash theme={null}
echo '{"status":"completed","completed_at":"2026-05-12T14:32:00Z"}' \
  | rulebase work-items update 0197ac58-70c9-7bc0-9ba8-1bb7ce8f78cc --file -
```

### `work-items delete`

Soft-deletes a work item and requires `--yes`, so no script deletes anything by
accident:

```bash theme={null}
rulebase work-items delete 0197ac58-70c9-7bc0-9ba8-1bb7ce8f78cc --yes
```

The call is idempotent: deleting an already-deleted work item still succeeds and
reports `"deleted": false`.

### `work-items apply`

The batch workhorse. Creates, updates, or restores work items by `external_id`.

```bash theme={null}
rulebase work-items apply --file work-items.json
cat work-items.json | rulebase work-items apply --file -
rulebase work-items apply --file work-items.json --dry-run
```

The canonical input is the same envelope the API takes:

```json theme={null}
{
  "work_items": [
    { "external_id": "TASK-9182", "agent_email": "alex@example.com", "type": "fraud_review" },
    { "external_id": "TASK-9183", "type": "kyc_verification", "custom_attributes": { "risk_score": 12 } }
  ]
}
```

A bare array is normalized to that envelope, so `[{...},{...}]` works too. Input
is capped at 25 MB.

The API accepts 100 items per request; the CLI splits anything larger into
chunks of 100 automatically and stitches the responses back together, so
`index` values in the output are positions in **your** input, in your order:

```json theme={null}
{
  "ok": true,
  "data": {
    "dry_run": false,
    "results": [
      { "index": 0, "external_id": "TASK-9182", "status": "updated", "data": { "id": "0197..." } },
      { "index": 1, "external_id": "TASK-9183", "status": "created", "data": { "id": "0198..." } }
    ],
    "summary": { "total": 2, "created": 1, "updated": 1, "unchanged": 0, "restored": 0, "error": 0 }
  },
  "meta": { "chunks": 1, "chunk_size": 100 }
}
```

Item statuses are `created`, `updated`, `unchanged`, `restored`, or `error`. If
any item fails, the successful items are still applied, the envelope reports
`"ok": false` with `error.code` `partial_failure`, the full `results` array is
still present, and the exit code is `1`.

`--dry-run` returns the same shape with `"dry_run": true` and writes nothing.

### `work-items schema`

Prints the bundled JSON Schema for the three input shapes, so an agent can
validate before sending:

```bash theme={null}
rulebase work-items schema | jq '.data.work_items_batch'
```

The bundle ships with the package and is checked against the published OpenAPI
document on release, so it cannot silently drift from the API.

## Agent and CI recipes

Reconcile a day of records, failing the job if any item is rejected:

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail
export RULEBASE_API_KEY="$RULEBASE_API_KEY"   # from your secret store

./export-work-items.sh > /tmp/work-items.json

rulebase work-items apply --file /tmp/work-items.json --dry-run > /tmp/plan.json
jq '.data.summary' /tmp/plan.json

rulebase work-items apply --file /tmp/work-items.json > /tmp/result.json
```

`set -e` turns the exit code into a failed job: `1` means some items were
rejected (inspect `/tmp/result.json` for the `error` entries), `3` means the
credential is missing or pointed at the wrong region, `4` means the API or the
network failed.

Pipe a single record straight out of another tool:

```bash theme={null}
jq -n '{external_id:"TASK-9182",type:"fraud_review"}' \
  | rulebase work-items create --file -
```

Collect every completed work item from a date:

```bash theme={null}
rulebase work-items list --all --status completed \
  --completed-after 2026-05-12T00:00:00Z \
  | jq '.data | length'
```

## JSON apply vs CSV upload

Both write work items; they suit different sources.

|          | `work-items apply`                                     | CSV upload                   |
| -------- | ------------------------------------------------------ | ---------------------------- |
| Input    | JSON objects you already hold                          | A CSV export                 |
| Endpoint | `POST /v1/work_items/batch`                            | `POST /v1/work_items/upload` |
| Timing   | Synchronous                                            | Asynchronous job you poll    |
| Feedback | Per item: created, updated, unchanged, restored, error | Per file: row counts         |
| Preview  | `--dry-run`                                            | None                         |
| Events   | Inline per work item                                   | Not supported                |

Use apply from agents and services. Use [CSV upload](/guides/uploads/work-items)
when a human or a legacy system produces a spreadsheet.

## Behaviour notes

**Retries and idempotency.** Apply is keyed on `external_id`, so re-running the
same input is safe: items that did not change come back as `unchanged`. `create`
is not idempotent: the second attempt returns `conflict` so you find
out that you sent the same record twice. The CLI retries `429` and transient
network failures a few times with backoff, honouring `Retry-After`; when it
gives up, `error.details.retry_after` carries the seconds the API asked for.

**Rate limits.** The API allows roughly 5,000 requests per minute per
organization. Batching 100 items per request keeps a large backfill
comfortably below it.

**Deletion and restoration.** `delete` is a soft delete: the work item leaves
reads and lists and stops being summarized, while evaluations that reference it
keep resolving. Applying the same `external_id` again restores it and reports
`restored`. There is no hard delete and no bulk delete, and items you leave out
of an apply are never deleted.

**Credential safety.** Keys are only ever read from the environment or the
`0600` credential file, never from argv. Output is redacted: the CLI prints a
key family and length (`rk_live_…32 chars`), never the key.

## Troubleshooting

**`unauthorized` (exit 3 or 4) that looks like a bad key.** Nine times out of
ten this is the region. A key issued in EU returns the same `401` against US as
a revoked key does. Run:

```bash theme={null}
rulebase doctor
```

`doctor` tries both regions and names the one that accepts your key; then pass
`--region eu` or set `RULEBASE_REGION=eu`.

**`credentials_ambiguous` (exit 3).** Keys are stored for both regions. Pass
`--region`.

**`invalid_json` or `invalid_input` (exit 2).** The file or stdin did not parse,
or an item is missing a required field. Compare against
`work-items schema`; note that `completed_at` is required whenever `status` is
`completed` or `cancelled`.

**Exit 1 with `partial_failure`.** The request reached the API and most items
landed. Look at the entries in `data.results` whose `status` is `error`; their
`error` message names the field at fault. Fix and re-apply; items that already
landed come back `unchanged`.

## Related

* [Set up API access](/guides/api-access)
* [Uploading work items](/guides/uploads/work-items)
* [Work item API reference](/api-v2-reference/apply-work-items)
