Skip to main content
Headless CLIScripting + CI
Headless CLI

Scripting + CI

Turn common store operations into reliable CI jobs and repeatable runbooks.

Exit codes, idempotency, GitHub Actions example, polling vs webhooks, error handling, concurrency.

Patterns for using the CLI from shell scripts, CI pipelines, and local automation. Stay --json-first, pipe to jq, and treat exit codes seriously.

Exit codes

CodeMeaning
0Success.
1Unknown / generic error.
2Invalid arguments.
3Authentication failure (401).
4Authorization failure (403, scope mismatch).
5Not found (404).
6Conflict / state guard (409, 422).
7Rate limited (429).
8Network / transport.

Idempotent operations

Always set --idempotency-key on state-changing commands in CI. A retried script run with the same key replays cleanly:

bash
KEY="ci-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" aly-store-cli orders fulfill ord_72...4b \  --carrier USPS --tracking 940011... \  --idempotency-key "$KEY"

GitHub Actions example

.github/workflows/import-products.ymlyaml
name: Import productson:  workflow_dispatch: jobs:  run:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4      - uses: oven-sh/setup-bun@v1      - run: bun install      - name: Import        env:          ALY_API_KEY: ${{ secrets.ALY_API_KEY_PROD }}        run: |          jq -c '.[]' products.json | while read row; do            slug=$(echo "$row" | jq -r .slug)            name=$(echo "$row" | jq -r .name)            price=$(echo "$row" | jq -r .price_cents)            bun tools/aly-store-cli.ts products create \              --site-slug acme \              --slug "$slug" \              --name "$name" \              --price "$price" \              --idempotency-key "import-$slug" \              --direct --json          done

Polling vs webhooks

For event-driven work, prefer webhooks — register an endpoint, let Aly push. Polling is fine for one-shot scripts but never for a long-running loop; you'll hit the rate limit.

Error handling

bash
set -euo pipefail # Fail fast on auth issues, but absorb a transient rate limitif ! out=$(aly-store-cli orders list --status pending --json); then  case $? in    3|4) echo "Auth problem — fix the key"; exit 1;;    7)   echo "Rate limited — retrying in 30s"; sleep 30;         out=$(aly-store-cli orders list --status pending --json);;    *)   echo "Unknown failure"; exit 1;;  esacfiecho "$out" | jq '.[].id'
Lock the transport in CI
Pin --direct in CI scripts. The MCP transport is useful for human + agent parity but adds latency and JSON-RPC framing overhead. Direct mode is consistently faster and easier to debug.

Concurrency

The per-principal rate limit applies to the bearer, not the process. Parallel xargs with high concurrency will hit it fast. Cap at 5 concurrent calls in CI; back off on a 429.

Updated

Was this page helpful?