Scripting and CI
Run the BankSync CLI non-interactively in scripts and CI: API key auth, JSON output, stable exit codes, and machine-readable errors.
4 min read
On this page
The CLI is built to be scriptable first. In a non-interactive context it authenticates from an environment variable, emits JSON, never prompts, and reports failure through stable exit codes and machine-readable errors, so a script or pipeline can branch on results without parsing prose.
Authenticate without logging in#
In CI you do not run login. Provide the key through BANKSYNC_API_KEY and every command uses it:
export BANKSYNC_API_KEY="bsk_your_api_key_here"banksync banks listStore the key as a secret in your CI provider, never in the repository. Use a least-privilege key: a reporting job needs only read scopes.
Output is JSON when piped#
Because the default format is JSON whenever stdout is not a terminal, scripts get structured output with no flag. You can still be explicit with --json:
banksync banks list | jq '.data | length'banksync tx list --bank amex --json > txns.jsonThe top-level shape is always { "data": ..., "meta": ... }, so .data is a reliable path. See output formats for the full envelope.
The CLI never blocks in CI#
When there is no terminal, the CLI behaves as if --no-input were set: it never prompts and never waits. A missing required argument becomes an error, not a hang. CI=true is treated as non-interactive too. Destructive commands refuse to run without --yes:
banksync feeds delete fed_123 --yesUse --quiet to suppress spinners and notices if you want data only, though these already go to stderr and are auto-suppressed when piped.
Exit codes#
Every command exits with a stable code that classifies the outcome. Branch on these rather than parsing messages:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic or unexpected error (including 5xx) |
| 2 | Invalid usage or bad request (400) |
| 3 | Unauthenticated or forbidden (401 or 403) |
| 4 | Resource not found (404) |
| 5 | Conflict, for example a sync already running (409) |
| 6 | Semantic validation failed (422) |
| 7 | Rate limited (429) |
| 8 | Provider does not support this capability (501) |
| 9 | Backend unavailable or consent required (503) |
The same map is available at runtime from banksync commands --json under exit_codes, so an agent or script can read it without hard-coding.
Machine-readable errors#
In any non-table format (or when piped), an error is a single JSON object on stderr while stdout stays empty. The shape is stable:
{ "error": { "code": "conflict", "message": "A sync is already running for this feed.", "status": 409, "hint": "A conflicting operation is in progress. For syncs, pass --force.", "retry_after": 60 }}code is a stable snake_case identifier (never the raw API message), status is the HTTP status when there was one, hint is a short suggestion, and retry_after (seconds) appears on rate-limit errors. On a terminal you instead get a short human Error line with a dim hint.
Retries#
Idempotent GET requests retry automatically on rate limits (honoring Retry-After) and transient network errors, with backoff. Control it with --max-retries <n> or disable it with --no-retry. Non-GET requests are never retried.
A bash script#
This example reads data and fails cleanly on any error:
#!/usr/bin/env bashset -euo pipefail
export BANKSYNC_API_KEY="${BANKSYNC_API_KEY:?set BANKSYNC_API_KEY}"
# Fail the script if the CLI exits nonzero; capture JSON for processing.banks=$(banksync banks list --json)echo "$banks" | jq -r '.data[] | "\(.id)\t\(.name)\t\(.connectionStatus.status)"'
# Branch on a specific failure class using the exit code.if ! banksync feeds sync fed_123 --watch; then code=$? if [ "$code" -eq 5 ]; then echo "A sync was already running; passing --force" >&2 banksync feeds sync fed_123 --force --watch else echo "Sync failed with exit code $code" >&2 exit "$code" fifiGitHub Actions#
Install the CLI, authenticate from a secret, and export a report artifact:
name: Nightly transactions exporton: schedule: - cron: "0 6 * * *"jobs: export: runs-on: ubuntu-latest steps: - name: Install the BankSync CLI run: curl -fsSL https://banksync.io/install.sh | sh - name: Export transactions env: BANKSYNC_API_KEY: ${{ secrets.BANKSYNC_API_KEY }} run: | banksync tx list --bank amex --all -o ndjson > transactions.ndjson - uses: actions/upload-artifact@v4 with: name: transactions path: transactions.ndjsonBecause the runner is non-interactive, the CLI emits NDJSON without a format flag; the explicit -o ndjson here makes the intent clear and stable.
Next steps#
- For agents: the same guarantees, framed for AI agents.
- Output formats: the JSON envelope and NDJSON streaming.
- Telemetry: what is collected and how to opt out in CI.
Use this page with your AI assistant
Every BankSync doc is available as plain Markdown for agents and LLMs.