# Use the Humanbased CLI


The `hb` command is a terminal client for Campaign API operations and delivered-record reads. `humanbased` is an alternate executable name. The repository's current package identifies itself as `0.1.0-next.3`; that source version is not evidence of a publicly downloadable release. Ask your organization's administrator or [Humanbased support](mailto:hello@humanbased.ai) for an approved executable and the environment it supports.

If your organization has authorized a checkout of the Humanbased monorepo, developers can build that checkout with Node.js 20 or newer and the repository's pnpm workspace:

```sh
pnpm install --frozen-lockfile
pnpm --filter @humanbased/cli build
node packages/cli/dist/hb.js --version
```

Run those commands from the repository root. For the examples below, replace `hb` with `node packages/cli/dist/hb.js` when using the source build. A source build is a developer route, not a public package installation.

## Discover this build before using it

```sh
hb --version
hb --help --json
hb config profiles set production --environment production --api-url https://api.humanbased.ai
hb config profiles use production
hb config profiles list --json
```

The JSON help inventory and profile commands make no network request. The profile records an API origin, not a credential; the CLI has no default destination. For staging, create a separate profile with `--environment staging --api-url https://api.staging.humanbased.ai`. Check the current profile before each environment change.

Use `hb auth login` for the supported browser sign-in flow, or provide an authorized organization key through `HB_API_KEY` as described below. Then run:

```sh
hb auth status
hb auth whoami --json
hb capabilities --json
```

Check the selected environment, API origin, organization, and granted capabilities. A key supplied through `HB_API_KEY` takes precedence over a saved session. The CLI refuses a missing or mismatched destination rather than guessing one.

## Choose a credential

A person can use `hb auth login` on a supported build to approve a sign-in in the browser. The resulting CLI session has narrower authority than a full portal session. For unattended automation, supply an organization API Key through the runtime's secret mechanism as `HB_API_KEY` or `--token-stdin`. Do not place the Key in a command line, repository, profile, or agent prompt. Keys and browser sessions are different credentials with different permissions.

The server still decides whether the selected credential can read or change a particular Campaign. A capability in a local help listing is not proof of a grant. A `403` calls for `hb auth whoami --json`, a check of the active organization, and review of that Campaign's permissions—not a broader retry with another person's credential.

## Read first and check the result

For a read-only starting point, list the Campaigns visible to the selected identity:

```sh
hb campaign list --limit 5 --json
```

The machine response is one JSON envelope on standard output. Check its `error.code` and the process exit code; progress goes to standard error. An empty `data.items` means no Campaign was visible in that scope. If `data.has_more` is true, pass the returned `data.next_cursor` as `--cursor` for the next page. `--all` deliberately walks multiple pages, so use it only when that larger read is intended.

| Exit code | Meaning |
| --- | --- |
| `0` | The command met its completion rule; a receipt read can still report a failed write. |
| `1` | The request completed, but the command's completion rule did not hold: a document that fails validation, a plan that cannot be applied, or a `campaign records pull --all` export that stopped early. |
| `2` | Invalid command arguments or a malformed request. |
| `3` or `4` | Missing credential or insufficient permission. |
| `5` | Conflict or outcome requiring a person's decision. |
| `6` | Transient failure, or output that could not be delivered; follow the command's retry guidance. |
| `7`–`10` | Backend failure, spend limit, wait timeout, or contract mismatch; inspect `error.code`. |
| `130` | Interrupted. |

When you finish with a browser session, use `hb auth logout`. `hb auth sessions list` and `hb auth sessions revoke` let you inspect and revoke saved sessions; an API Key is managed separately by its organization.

## Commands for Campaign writes and launch

Every Campaign write follows the same order: write a candidate file, validate it, plan it, then apply it under a request key you choose. Each command below wraps the [Campaign API](/api/campaign-api) operation for that step; that page carries the request, receipt, and error contract.

| Step | Command | Grant |
| --- | --- | --- |
| Start a candidate around a template | `hb campaign init --template <template-id> --out campaign.json` | `campaign:read` |
| Start from an existing version | `hb campaign init --from-version <campaign-id> --out campaign.json` | `campaign:read` |
| Validate the candidate | `hb campaign validate --file campaign.json` | `campaign:author` |
| Plan a new Campaign | `hb campaign plan --create --file campaign.json --out plan.json` | `campaign:author` |
| Plan a change to a Campaign | `hb campaign plan <campaign-id> --file campaign.json --out plan.json` | `campaign:author` and `campaign:read` |
| Create the Campaign | `hb campaign create --file campaign.json --plan plan.json --idempotency-key <your-key>` | `campaign:author` |
| Apply the change | `hb campaign apply <campaign-id> --file campaign.json --plan plan.json --idempotency-key <your-key>` | `campaign:author` |
| Propose a launch | `hb campaign launch propose <campaign-id>` | `campaign:launch` |
| Run a confirmed launch | `hb campaign launch execute --proposal <proposal-id> --idempotency-key <your-key>` | `campaign:launch` |

A starter written by `init --template` leaves every value you must decide, such as the budget, the reward, and the caps, as a `<DECIDE: …>` placeholder. Replace each one before you validate. The CLI refuses to send a file that still contains a placeholder and exits `1` with `PLACEHOLDERS_UNANSWERED`.

`validate` and `plan` change nothing. Both exit `1` when the candidate is invalid or cannot be applied, so a script can stop there. `plan --out` saves the reviewed change set; `create --plan` and `apply --plan` re-plan before writing and refuse with `PLAN_DRIFTED` when the file or the base has changed since the review.

`--idempotency-key` names one write. Re-sending the same key with the same request never writes twice: the server answers with the first attempt's outcome. Choose a new key for a different change.

For an apply whose response was lost, inspect the original request key:

```sh
# Campaign-file apply (the default action)
hb campaign actions inspect --request-key <your-key> --json

# Template apply
hb campaign actions inspect --request-key <your-key> --action template.apply --json
```

The lookup searches one action at a time. Without `--action` it looks for `campaignfile.apply`, so a template apply needs `--action template.apply`; otherwise it reports no receipt, or the result of a different write that used the same request key. This lookup covers Campaign-file and template applies. It does not cover launch; for a lost launch response, resend the identical launch intent under the **same** idempotency key so the server can replay its first result. `uncertain` is an unresolved outcome requiring human review of the receipt steps and Campaign state, not a reason to try a new key.

Launching commits the Campaign's budget, so it takes three steps and two actors. `hb campaign launch propose` records a proposal and prints the address of the Developer portal's confirmation page; with `--json` the address is in `data.confirmation_url`. A person who can edit that Campaign signs in to the portal, reads the proposal, and selects **Confirm this launch**. Then `hb campaign launch execute` runs the confirmed proposal once. The CLI has no command that confirms, and a session from `hb auth login` can neither propose nor execute a launch: both commands need an organization API Key.

## Confirm which environment you are pointed at

Confirm it before every write.

```sh
hb config profiles show --json
```

The `environment` and `api_url` in the output are **what this profile says**. The destination of the next command resolves field by field, each taking the first of these that has a value:

| This value | First of these with a value |
| --- | --- |
| Which profile | `--profile` → `HB_PROFILE` → the saved default profile |
| `api_url` | `--api-url` → `HB_API_URL` → the profile selected above |
| `environment` | `--environment` → `HB_ENVIRONMENT` → the profile selected above |

`--profile` only decides which profile supplies the fallback; it ranks below `HB_API_URL` and `HB_ENVIRONMENT`. So `--profile staging` together with an `HB_API_URL` pointing at production follows the variable.

`environment` and `api_url` also have to agree: each environment admits only its own origins, and a mismatch refuses the command with `ORIGIN_MISMATCH` rather than printing a warning. An `HB_API_URL` left alone in the shell is therefore refused on the spot. The case that runs silently against the wrong environment is the other one: `HB_API_URL` and `HB_ENVIRONMENT` pointing as a pair at the same other environment — the two values agree, the profile takes no part, and `hb config profiles show` still reads staging.

The credential is a separate line. `hb auth status` reports which credential the next command would actually use, and says so when `HB_API_KEY` is shadowing a saved session.

| What it does | Command | Grant |
| --- | --- | --- |
| Every profile, with the current default marked | `hb config profiles list` | — |
| One profile; the current default when the name is omitted | `hb config profiles show <name>` | — |
| Create or replace a profile | `hb config profiles set <name> --environment <production, staging or local> --api-url <origin> --org <organization id>` | — |
| Make a profile the default | `hb config profiles use <name>` | — |
| Forget a profile | `hb config profiles remove <name>` | — |

None of the five makes a network request, and none of them touches a credential. A profile records where to go and which organization to expect; the credential arrives per run from the environment or standard input. So changing environment means changing both the profile and the credential.

## Sign in and sessions

None of these names a Campaign, so none of them takes a `campaign:*` grant; what the credential may actually do is still the server's decision.

| Step | Command | Grant |
| --- | --- | --- |
| Sign in through the browser | `hb auth login` | — |
| See which credential the next command will use | `hb auth status` | — |
| Confirm the caller the server verified | `hb auth whoami` | — |
| What this release supports and what this credential may do | `hb capabilities` | — |
| List the terminals signed in as you | `hb auth sessions list` | — |
| Revoke one of those sessions | `hb auth sessions revoke <session-id>` | — |
| End this machine's session | `hb auth logout` | — |

`hb auth status` makes no network request; it reads the session saved on this machine. The rest do.

## Read commands

Before a write and after a launch, these confirm what the server actually recorded. None of them writes.

| Question | Command | Capability |
| --- | --- | --- |
| What state is this Campaign in, and what can this credential do to it | `hb campaign get <campaign-id>` | `campaign:read` |
| Which stored versions does it have | `hb campaign versions list <campaign-id>` | `campaign:read` |
| The document of one version | `hb campaign versions inspect <campaign-id> --version <number> --out version.json` | `campaign:read` |
| What changed between two versions | `hb campaign versions diff <campaign-id> --from <number> --to <number>` | `campaign:read` |
| Counts for one event-time window, with their denominators | `hb campaign observation <campaign-id> --from <ISO timestamp> --to <ISO timestamp>` | `campaign:read` |
| What a candidate file would quote as reward | `hb campaign feasibility --file campaign.json` | `campaign:author` |
| Which Campaigns this credential can read | `hb campaign list --limit 5` | `campaign:read` |
| The receipt for one write | `hb campaign actions inspect --request-key <your-key>` | `campaign:author` |
| Records one Campaign delivered into one subscription | `hb campaign records pull <campaign-id> --subscription <subscription-id>` | — |

`records pull` carries no `campaign:*` grant, but it does need an organization API Key with data access; a browser session is refused. The walk-through is under “Export every delivered record” on this page.

`versions list` returns newest first; page with `--limit` and `--cursor`, or walk every page with `--all`.

`versions inspect` takes the newest version when `--version` is omitted. `--raw` adds the server's own fields and suits diagnosis; write the copy you mean to edit and send back with `--out`.

`observation` measures by event time: `--from` is inclusive, `--to` exclusive. Every count carries its denominator, and a numerator alone is not a rate.

`feasibility` quotes and creates nothing, and it names what it cannot answer.

## Template commands

A template decides which fields a candidate file has. `hb campaign init --template <template-id>` references a template from here.

| Step | Command | Capability |
| --- | --- | --- |
| Templates this organization may reference | `hb template list` | `campaign:read` |
| One template's field contract | `hb template get <template-id>` | `campaign:read` |
| Write a starting template document | `hb template init --kind <kind> --name <name> --out template.json` | — |
| Validate a template document | `hb template validate --file template.json` | `campaign:author` |
| Create or update a template | `hb template apply --file template.json --idempotency-key <your-key>` | `campaign:author` |

`template init` writes a file on this machine. It makes no request and needs no grant.

Updating an existing template takes both `--template-id` and `--base-hash`, the second from `hb template get`; `--template-id` alone is refused. Without it, when two people edit the same template in turn, the later write replaces the earlier one and neither of them can see that it happened.

A template apply has a receipt too, but the lookup needs `--action template.apply`; without it you get another write's result, or nothing at all.

This page does not cover the eight `hb dsa …` commands, which consult the Campaign design assistant, or `hb agent runs`. No page documents them yet.

## Export every delivered record

`hb campaign records pull` reads the records one Campaign has delivered into one subscription. It always replays from the first record, so it never moves the subscription's incremental reading position. The Campaign filter can only narrow what the subscription already authorizes: for a priced subscription that is the range already acquired, and in every case it is delivered records, not all of the Campaign's submissions. Confirm that `hb --help --json` lists the command in your build; with a build that lacks it, page through the [HTTP data API](/api/api-data).

### Credential and a first page

The command needs a full organization API Key with data access to the subscription. A session from `hb auth login` is refused. A Key with only the Data grant can still run `hb capabilities`; it reports the Campaign API catalog as unavailable, which has no effect on this command. Set `HB_API_KEY` and `HB_SUBSCRIPTION_ID` as in [Get your first authorized data](/api/api-data), then pass the Key to this one command:

```sh
printf 'Campaign ID: '
read -r HB_CAMPAIGN_ID
HB_API_KEY="$HB_API_KEY" hb campaign records pull "$HB_CAMPAIGN_ID" \
  --subscription "$HB_SUBSCRIPTION_ID" \
  --limit 5 --json
```

Without `--all`, the command reads one page and returns `data.next_cursor`. `--limit` sets the page size: 50 by default, 200 at most. The command takes exactly one Campaign ID.

### Export all pages

`--all` follows the cursor until the server reports no more records. Send the result to a private directory:

```sh
HB_EXPORT_DIR="$(mktemp -d)"
printf 'Export directory: %s\n' "$HB_EXPORT_DIR"
HB_API_KEY="$HB_API_KEY" hb campaign records pull "$HB_CAMPAIGN_ID" \
  --subscription "$HB_SUBSCRIPTION_ID" \
  --limit 200 --all --json > "$HB_EXPORT_DIR/campaign-records.json"
printf 'Exit code: %s\n' "$?"
```

The records are in `data.items`. Treat the file as a complete export only when both checks pass: the exit code is `0`, and `data.complete` is `true`.

| `data` field | Meaning |
| --- | --- |
| `items` | The records this command read, in the order the server returned them. |
| `count` | The number of records in `items`. |
| `pages_read` | The number of pages this command requested. |
| `has_more` | Whether the server still reported more records when the command stopped. |
| `next_cursor` | Where a later command can continue, or `null`. |
| `complete` | `true` only when the command started at the first record, without `--cursor`, and reached the last page. |
| `pagination_stop_reason` | `null`, or the reason an `--all` read stopped early. |

### Complete and partial results

A complete export looks like this. Both examples omit `data.items` and the envelope's `requestId`, `context`, and `notices`; the identifiers are illustrations.

```json
{
  "apiVersion": "humanbased.cli/v1",
  "command": "campaign.records.pull",
  "requestId": "e9dc2862-d165-44ce-b54a-42111284bd40",
  "context": {
    "profile": null,
    "apiOrigin": "https://api.humanbased.ai",
    "organizationId": "10571000020000000001"
  },
  "data": {
    "subscription_id": "784210003001",
    "campaign_id": "784210001001",
    "count": 1342,
    "pages_read": 7,
    "has_more": false,
    "next_cursor": null,
    "complete": true,
    "pagination_stop_reason": null
  },
  "notices": [],
  "error": null
}
```

A partial export exits `1`. It still carries the records read so far, and it names why it stopped:

```json
{
  "apiVersion": "humanbased.cli/v1",
  "command": "campaign.records.pull",
  "requestId": "e9dc2862-d165-44ce-b54a-42111284bd40",
  "context": {
    "profile": null,
    "apiOrigin": "https://api.humanbased.ai",
    "organizationId": "10571000020000000001"
  },
  "data": {
    "subscription_id": "784210003001",
    "campaign_id": "784210001001",
    "count": 100000,
    "pages_read": 500,
    "has_more": true,
    "next_cursor": "example-opaque-cursor",
    "complete": false,
    "pagination_stop_reason": "item_limit"
  },
  "notices": [],
  "error": {
    "code": "PAGINATION_INCOMPLETE",
    "httpStatus": null,
    "message": "The --all walk stopped while the server still reported more pages. The partial data and a continuation cursor, when available, are included.",
    "retryable": false,
    "nextAction": null,
    "details": null
  }
}
```

| `pagination_stop_reason` | What happened | What to do now |
| --- | --- | --- |
| `page_limit` | The command read 1,000 pages. At the default page size of 50, that is 50,000 records. | Continue from `next_cursor`. Pass `--limit 200` to read more records per run. |
| `item_limit` | The command read 100,000 records. | Continue from `next_cursor`. |
| `missing_cursor` | The server reported more records and returned no cursor. | `next_cursor` is `null`, so this file has no safe place to continue from. Keep it for diagnosis and run the export again from the first record. |
| `repeated_cursor` | The server returned a cursor this command had already used. | `next_cursor` is `null`. Never resubmit the repeated cursor. Run the export again from the first record or from an earlier cursor you saved, and contact [hello@humanbased.ai](mailto:hello@humanbased.ai) if it repeats. |

### Continue a partial export

Continue only when `data.next_cursor` is a string. Copy its value without the JSON quotes:

```sh
printf 'Part number, 2 for the first continuation: '
read -r HB_PART
printf 'Next cursor: '
read -r HB_CURSOR
HB_API_KEY="$HB_API_KEY" hb campaign records pull "$HB_CAMPAIGN_ID" \
  --subscription "$HB_SUBSCRIPTION_ID" \
  --cursor "$HB_CURSOR" \
  --limit 200 --all --json > "$HB_EXPORT_DIR/campaign-records-$HB_PART.json"
printf 'Exit code: %s\n' "$?"
```

One run reads at most 100,000 records, so a large export needs several continuations. Save each one to its own file: part 2, then part 3, and so on. A continuation saved over an earlier file loses that file's records.

A continuation holds only the records after the cursor. Its `data.complete` stays `false` even when it reaches the last page and exits `0`. The whole export is the first file's `data.items` followed by the `data.items` of each continuation, with the same Campaign and subscription in every command.

### Protect the output

| Part of the export | What it means for you |
| --- | --- |
| Record content | `data.items[].data` is written exactly as delivered, including any value that happens to look like a credential. Store the file under the same rules as the source data. |
| Numbers | Numbers inside record content keep their original digits, including integers beyond JavaScript's safe range and long decimals. Use a parser that preserves them when your values need that precision. |
| Temporary file | While it reads, the CLI stages the records in a file in the system temporary directory that only the current user can read, and deletes it when the command ends. Leave enough free space there for the whole export. |
| Interrupted output | When the pipe closes or the destination fills up, the command exits with a non-zero code and reports the failure on standard error. A JSON file that was already partly written is incomplete: discard it and run the export again. |

### Errors

| Exit code and `error.code` | Cause | What to do now |
| --- | --- | --- |
| `1`, `PAGINATION_INCOMPLETE` | An `--all` read stopped while more records remained. | Read `data.pagination_stop_reason` and continue as described above. |
| `2`, `INVALID_ARGUMENT` | The Campaign ID is blank or a comma-separated list, or an ID or cursor looks like a credential. Nothing was sent. | Pass one Campaign ID and the real identifiers. Supply the Key only through `HB_API_KEY` or `--token-stdin`. |
| `3` | No usable credential: the Key is missing, malformed, expired, or revoked, or a sign-in session was used. | Supply a full organization API Key. |
| `4` | The Key has no data access, or the subscription is outside its scope or organization. | Check the Key's Data grant in [Create and use an API Key](/api/api-auth). |
| `6` | The service was unavailable, or the output could not be delivered. | Run the same command again; a replay charges nothing and moves no position. |

For API operations and grants, see [Campaign API](/api/campaign-api). To hand one bounded read to an agent, see [Use an agent with Humanbased](/api/agent-access).
