CLI
The AGLedger CLI is a thin cover over the API. Every route the API exposes is reachable through one pass-through command, agledger api <METHOD> <path>, so the CLI never drifts behind the API and the API reference is also the CLI reference. A small set of CLI-local commands handle the things a pass-through cannot: storing credentials, cold-start discovery, and verifying an audit export offline.
Use this guide when you want to notarize from a shell, script a chain in CI, or hand an agent a terminal instead of an SDK. For request and response shapes, follow the API reference - this page covers which command maps to which job and how the offline path works.
The CLI is published as @agledger/cli v1.4.0 and requires Node 24 or later.
Install
npm install -g @agledger/cli
agledger --version
On Linux hosts where the global npm prefix is root-owned (the NodeSource default), npm install -g
fails with EACCES - run it with sudo, or point npm at a user-writable prefix first
(npm config set prefix ~/.npm-global).
Air-gapped install. The CLI is a single npm package with two runtime dependencies (@oclif/core, @agledger/verify-core) and no network calls of its own beyond the API requests you make. On a connected machine, pack it and carry the tarball across:
npm pack @agledger/cli
# produces agledger-cli-<version>.tgz — copy it to the target host, then:
npm install -g ./agledger-cli-<version>.tgz
The agledger verify command runs entirely offline once installed and never contacts a registry, our website, or any AGLedger Server.
Point the CLI at your Server
Every command resolves its Server URL and key from, in order: explicit --api-url / --api-key flags, the AGLEDGER_API_URL / AGLEDGER_API_KEY environment variables, or a stored login profile. For a single shell session, environment variables are the simplest:
export AGLEDGER_API_URL=https://agledger.example.com
export AGLEDGER_API_KEY=agl_agt_...
There is no default Server - point the CLI at your own instance. The CLI talks only to the URL you give it.
Authenticate
agledger login verifies a key against your Server (it calls GET /v1/auth/me) and, on success, stores it in ~/.agledger/config.json with 0600 permissions. Use named profiles to hold keys for more than one Server or environment on the same machine:
agledger login --api-key agl_agt_... --api-url https://agledger.example.com --profile prod
Switch and inspect profiles with config:
agledger config list # all stored profiles, marking the active one
agledger config use prod # set prod as the active profile
agledger config get # show the active profile (URL only, never the key)
agledger config path # print the config file path
Check login state without failing - auth returns authenticated: false and exit code 0 when no key is configured, which is convenient for scripts that gate on it:
agledger auth
Remove a stored credential with agledger logout --profile prod, or agledger logout --all to clear every profile.
Keys are minted and rotated on the Server, not by the CLI. For how to obtain an agl_ key or an OIDC-bound certificate, see Authentication.
Confirm connectivity
agledger discover is the cold-start call. It checks Server health and your resolved identity, and returns the quickstart workflow. Partial results come back even if one call fails, so you can tell what is reachable:
agledger discover
Notarize a record
Creating a record is POST /v1/records through the pass-through command. For a notarize-only contract type the record terminalizes in this one signed call - this is the on-ramp.
agledger api POST /v1/records --data '{
"type": "invoice-processed-v1",
"criteria": { "invoiceId": "INV-4815", "amount": 2400 }
}'
You can build the body from typed -F fields instead of a JSON string. Dots nest, [] appends to an array, and values are typed (true / false / null, numbers, or bracketed JSON):
agledger api POST /v1/records \
-F type=invoice-processed-v1 \
-F criteria.invoiceId=INV-4815 \
-F criteria.amount=2400
-F types its values, which is wrong for an identifier that happens to be all
digits. The Server does not coerce the fields of a JSON body, so a field declared
string refuses a number and -F externalTaskId=4821 is rejected. Use -f /
--raw-field to send a value verbatim as a string:
agledger api POST /v1/records \
-F type=invoice-processed-v1 \
-f criteria.invoiceId=4815 \
-F criteria.amount=2400
Path syntax is identical and the two forms parse together, so -F a.b=1 -f a.c=2
builds one tree. publisher, platformRef, projectRef, externalTaskId and
correlationId are declared strings carrying identifiers minted by other systems,
which makes them the fields most likely to arrive all-digit.
For larger payloads, read the body from a file or stdin:
agledger api POST /v1/records --input record.json
cat record.json | agledger api POST /v1/records --input -
Body sources compose in this precedence (low to high): --data, then --input, then -F and -f, then --query. Later sources override overlapping keys, so you can keep a base file and override one field on the command line.
To see exactly what would be sent without calling the Server, add --dry-run:
agledger api POST /v1/records --input record.json --dry-run
Retry a write without recording it twice
Every POST the CLI sends carries an Idempotency-Key, generated per invocation.
A fresh invocation therefore mints a fresh key and is a new write. When you are
retrying a call that may already have reached the Server, pass the first attempt's
key so the Server replays the original result instead of creating a second record:
agledger api POST /v1/records --input record.json \
--idempotency-key 4f1c8ad2-93e0-4a77-b8d1-6c2e5f0a91b4
The key binds to method, route and body, so a retry that changes the body is
rejected rather than replaying the earlier response against a different payload.
Only POST is affected: the 18 routes that opt into idempotency are all POST,
and the header is ignored elsewhere. --dry-run names the key it would send.
Submit a completion (gated contract types)
A contract type that declares a completion schema runs the gated lifecycle: create, accept, completion, verdict. The performer submits evidence as a Completion against the record:
agledger api POST /v1/records/<record-id>/completions \
--data '{"evidence":{"itemsDelivered":"copper wire","quantityDelivered":500}}'
The principal then renders the verdict with POST /v1/records/<record-id>/verdict (body {"completionId":"<id>","verdict":"accept"}). The CLI carries these calls because it is a pass-through; the lifecycle, fields, and states are owned by the API reference.
List and stream records
For GET and DELETE, parameters go to the query string rather than the body:
agledger api GET /v1/records -F status=ACTIVE -F limit=50
To walk every page of a paginated route, add --paginate. The CLI follows the cursor and streams each item as newline-delimited JSON (NDJSON), one record per line, which pipes cleanly into jq or a file:
agledger api GET /v1/records --paginate > records.ndjson
Export a chain
A record's full audit chain is exported through the same pass-through command. Write it to a file so you can verify it later, on this host or an air-gapped one:
agledger api GET /v1/records/<record-id>/audit-export > audit-export.json
The export is format 2.0: each entry is a COSE_Sign1 envelope (RFC 9052) over an in-toto v1 Statement, hash-chained and Ed25519-signed. These are the Signed Statements that make the chain verifiable by anyone holding the public keys, with no access to the Server.
Verify an export offline
agledger verify checks an export with no network access and no API key. It recomputes the hash chain, decodes each COSE_Sign1 envelope, and verifies every Ed25519 signature against the public keys. This is the default verification path - it runs the same on an air-gapped host as on a connected one.
agledger verify audit-export.json
The export embeds the Server's signing public keys, so a self-contained export verifies on its own. To verify against keys you hold out-of-band instead of trusting the embedded set, supply them as a JSON map of signingKeyId to base64 SPKI-DER public key:
agledger verify audit-export.json --keys vault-keys.json
To reject any export signed by an unexpected or retired key, pin the expected key id - every entry must reference it or verification fails:
agledger verify audit-export.json --require-key-id <key-id>
Read the export from stdin with -, and emit a machine-readable result with --json:
cat audit-export.json | agledger verify - --json
A passing run reports the record id and that every entry verified; verify exits non-zero on any failure, so it gates a pipeline cleanly. When the chain is broken, the result names the first failing position and the failure class - for example a CHAIN_POSITION_GAP, a CHAIN_HASH_MISMATCH, a CHAIN_LINK_BROKEN, a CHAIN_SIGNATURE_MISSING_KEY, or a CHAIN_SIGNATURE_INVALID - so you can tell tampering from a missing key. The audit page owns the full code table.
Output, exit codes, and errors
Every command supports --json (the default when stdout is piped, so output is parseable in scripts automatically) and --quiet (suppress output, exit code only). Errors are written to stderr as a structured object - { "error": true, "code", "message", "suggestion", ... } - and API errors pass through verbatim, including the API's own suggestion and nextSteps.
Exit codes are semantic and stable across releases, which lets a script branch on the failure without parsing text:
| Exit code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Usage error (bad path, method, JSON, or field) |
| 3 | Auth error (401, or no key configured) |
| 4 | Forbidden (403, missing scope or role) |
| 5 | Not found (404) |
| 6 | Conflict (409) |
| 7 | Rate limited (429) |
| 8 | Server error (5xx) |
| 9 | Network error |
| 10 | Timeout |
Discover the rest
Because the CLI is a thin cover, the API reference is the command reference. To explore the surface from the terminal:
agledger list-commands # the ten CLI-local commands
agledger help-json api # arguments and flags for one command
agledger api GET /openapi.json # the full route catalog from your Server
Everything beyond the CLI-local commands - records, completions, schemas, webhooks, federation, admin - is reached with agledger api <METHOD> <path> using the exact path and body shape in the API reference.
Command surface, flags, and the offline-verify behavior on this page are drawn from @agledger/cli v1.4.0. The pass-through routes (/v1/records, /v1/records/{id}/completions, /v1/records/{id}/audit-export) and their request and response shapes are owned by the API reference; confirm field-level detail there against your own Server.