The hb command is a terminal client for CampaignA developer's data-production project and its settings 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 organizationThe Developer-side unit that holds Campaigns, data access, and funds's administrator or Humanbased support 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:
pnpm install --frozen-lockfile
pnpm --filter @humanbased/cli build
node packages/cli/dist/hb.js --versionRun 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
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 --jsonThe 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:
hb auth status
hb auth whoami --json
hb capabilities --jsonCheck 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:
hb campaign list --limit 5 --jsonThe 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 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:
# 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 --jsonThe 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.
hb config profiles show --jsonThe 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 submissionsThe record created when work is submitted. Confirm that hb --help --json lists the command in your build; with a build that lacks it, page through the HTTP data API.
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, then pass the Key to this one command:
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 --jsonWithout --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:
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.
{
"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:
{
"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 if it repeats. |
Continue a partial export
Continue only when data.next_cursor is a string. Copy its value without the JSON quotes:
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. |
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. To hand one bounded read to an agent, see Use an agent with Humanbased.