Atoa CLI
The Atoa CLI is a command-line tool for interacting with the Atoa payment API from your terminal. Create payments, manage customers, test webhooks, and inspect transactions — without leaving the shell, and without writing a script.
It’s scriptable for CI/CD, automated test pipelines, support workflows, and one-off operations.
Source code: github.com
Log in with your browser
atoa login opens the dashboard, you approve, done. No token to copy-paste.
Test webhooks locally
atoa webhooks trigger PAYMENTS_STATUS fires a fake event at your sandbox URL — no real payment needed.
Two credential types
Your browser login runs account commands; an SDK key runs payments/data commands.
Shell completion
TAB-completes commands, flags, profile names, and key IDs across bash, zsh, and PowerShell.
Prerequisites
- Node.js 20 or later — verify with
node --version - An Atoa merchant account and a desktop browser on the same machine (login is browser-based). No account yet? Run
atoa signup, or see the Getting Started guide.
Installation
npm
yarn
pnpm
npm install -g @atoapayments/atoa-cli
Verify the install:
atoa --version
The CLI stores credentials locally under ~/.atoa/auth/, readable only by
your user account.
First-time setup
Pair the CLI with your Atoa account through your browser:
atoa login
Your default browser opens the Atoa dashboard, you approve the request, and the CLI is logged in. If your account has multiple businesses, you pick one (or run atoa business use <id> afterwards). A profile is created, named after your business.
atoa login requires an interactive terminal (TTY) and a desktop browser
on the same machine — there is no headless/CI login path (see
CI / automation).
No account yet? atoa signup walks you through account creation (email + one-time code) and business onboarding — no prior login needed.
Confirm you’re logged in
atoa whoami
Signed in as Acme Ltd
Email jane@example.com
Profile acme
The dual-credential model
The CLI holds two independent credential types per profile:
atoa login (browser)
│
├─► Login session ──► account-management commands
│ business, sessions, keys, staff, roles,
│ kyb, payment-links, bank, stores
│
└─► SDK API key (optional, via `atoa keys create`)
──► payments / data commands
payments, refunds, customers, payment-methods,
card-on-file, webhooks, bank-feed, payouts, institutions
A single atoa login always creates the login session. The SDK key is optional — add it whenever you need the payments/data commands:
atoa keys create --env sandbox # mints a revocable SDK key
atoa keys create requires an admin role on the business. The apiSecret is stored locally under ~/.atoa/auth/, so the CLI picks it up automatically on later commands.
Which login does each command need?
| Auth required | Commands |
|---|---|
Login session (atoa login) | business, sessions, keys create/list, kyb, staff, roles, payment-links, bank, stores |
SDK key (atoa keys create) | payments, refunds, customers, payment-methods, card-on-file, webhooks, bank-feed, payouts, institutions |
Environments and profiles
A single machine can hold credentials for many businesses (profiles) and both environments (sandbox + production). The CLI keeps them separate so you can’t accidentally run a production refund while testing locally.
atoa profile list # see every profile and which envs are configured
atoa profile use acme # switch active profile
atoa whoami # confirm which account and profile are active
SDK keys are per-env and not interchangeable — a sandbox key won’t
work against production. Mint one per env with atoa keys create --env <env>.
Per-profile settings
CLI state is stored entirely under profiles. To change which env a profile defaults to, use atoa profile set. To wipe everything, use atoa reset.
Inspect state
atoa profile list # every profile + which envs each one has
atoa profile show # full metadata for the active profile
atoa profile show acme # metadata for a specific profile
Change the default env on a profile
atoa profile set env=production # active profile; prompts to confirm
atoa profile set env=sandbox --profile acme --yes # scripted / CI
The keys you can set:
| Key | Allowed values | Notes |
|---|---|---|
env | sandbox | production | Sets which env commands target by default. Prompts for confirmation; pass --yes to skip. Mint the env’s SDK key with atoa keys create --env <env> if you don’t have one yet. |
Reset everything
atoa reset --yes # wipe local profiles + tokens (local only)
atoa reset --revoke --yes # additionally revoke server-side keys (best-effort)
atoa reset is destructive — it removes every profile and clears
every stored credential for this CLI. Use --revoke only when you also want the
server-side keys deactivated (e.g. when offboarding a shared device).
Core workflows
Create a payment
atoa payments create \
--amount 25.00 \
--orderId order-001 \
--customerId cust_123
Returns a paymentRequestId, the customer-facing paymentLink, and a qrCodeUrl.
Check payment status (poll until terminal)
atoa payments status b7c8e3a1-4d29-4f5a-8e6b-1f3a2c4d5e7f --poll
--poll keeps polling every 5 seconds until the status is no longer PENDING, up to 3 minutes.
List recent transactions
atoa payments transactions --from 2026-01-01 --to 2026-01-31 --status COMPLETED
Refund a payment
atoa refunds create \
--paymentRequestId b7c8e3a1-4d29-4f5a-8e6b-1f3a2c4d5e7f \
--amount 5.00 \
--currency GBP \
--notes "duplicate charge" \
--yes
Manage customers and saved cards
atoa customers create --fullName "Jane Doe" --email jane@example.com --type INDIVIDUAL
atoa customers list --search jane
atoa payment-methods list --customer cust_123
atoa card-on-file charge \
--customerId cust_123 \
--paymentMethodId card_456 \
--amount 10.50 \
--orderId order-007
Account management
These run on your browser login — no SDK key needed.
Switch the active business
If your account owns several businesses, account commands operate on one at a time:
atoa business list # list businesses; marks the active one
atoa business use <businessId> # switch the active business (validated server-side)
Manage login sessions
Every atoa login registers a device session server-side. Audit and revoke them:
atoa sessions list # active CLI sessions for this account
atoa sessions revoke <deviceId> # revoke one (use --yes to skip the prompt)
Revoking the current device’s session logs this CLI out — you’ll need to
atoa login again.
SDK key lifecycle
atoa keys create --name "CI server" # mint a key with a label
atoa keys list # list keys for this account (never shows secrets)
atoa keys regenerate --env sandbox --yes # rotate the active env's key
atoa keys revoke <sdkAccessId> --yes # revoke a specific key (or omit the id to pick interactively)
Team, onboarding & business setup
Staff and roles
atoa roles list # role IDs available on this business
atoa staff list # current staff members
atoa staff invite \
--firstName Alice --lastName Smith \
--email alice@example.com \
--role <roleId> # role ID from `atoa roles list`
atoa staff invite … --store <storeId> # restrict to store(s); repeat --store for several
KYB (Know Your Business) verification
atoa kyb status # verification status for the active business
atoa kyb link # print the KYB dashboard deep-link
atoa kyb link --open # …and open it in your default browser
Bank accounts
atoa bank list # bank accounts on the active business
atoa bank add # interactive: pick bank, enter details, verify via OTP
atoa bank add --sortCode 040004 --accountNumber 12345678 \
--accountHolderName "Acme Ltd" --setPrimary
atoa bank get <bankAccountId>
atoa bank delete <bankAccountId> --yes
Stores
All stores subcommands need a browser login:
atoa stores list # list merchant stores
atoa stores get <storeId> # store details
atoa stores link-bank <storeId> --bank <bankAccountId> # attach a bank account to a store
atoa stores image ./logo.png --storeId <storeId> # upload/replace the store logo (PNG/JPG, ≤6MB)
Onboard a brand-new account
atoa signup # interactive: email + one-time code, then a guided onboarding wizard
atoa signup is the one command that needs no prior login — it creates
the account and logs you in as part of the flow.
Payment links
Create a hosted checkout link (browser login; --store-id is required):
atoa payment-links create --amount 10.50 --store-id <storeId>
atoa payment-links create --amount 10.50 --store-id <storeId> --notes "Invoice #42" # notes max 30 chars
atoa payment-links get <linkId> --store-id <storeId> # fetch a link + its status
atoa payment-links delete <linkId> --store-id <storeId> # remove a link
--amount is in GBP (e.g. 10.50 for £10.50), not pence.
Idempotency — duplicate-charge protection
The CLI auto-generates a fresh Idempotency-Key: <uuidv4> header on every POST/PUT/PATCH, so a network blip on the response never double-charges a customer. You don’t need to think about this for interactive use.
For CI / scripted retries — where one logical operation may run more than once (a job restarts) — pass a stable --idempotencyKey so a re-run produces the same key and the server deduplicates:
# Auto-generated key (default — safe for interactive use)
atoa refunds create --paymentRequestId pr_abc --amount 5.00
# Stable key (CI retry-safe — same key on every re-run of the same job)
atoa refunds create \
--paymentRequestId pr_abc --amount 5.00 \
--idempotencyKey "atoa-cli/refund/${RUN_ID}/${PAYMENT_ID}"
Available on atoa payments create, atoa refunds create, atoa card-on-file charge, and the generic atoa post. Every other POST still sends an auto-generated key, so all retries are safe by default.
Trigger test webhook events
The CLI can fire a synthetic webhook payload at your registered sandbox URL — same shape as production — without you actually creating a real payment.
atoa webhooks trigger PAYMENTS_STATUS
Test webhook triggered successfully.
Atoa’s webhook service dispatches the body to whatever sandbox URL you registered with atoa webhooks create. Inspect the request on your endpoint (e.g. webhook.site) to verify your signature-check, routing logic, and response shape work end-to-end.
Supported events
| Event | Description |
|---|---|
PAYMENTS_STATUS | Payment-status webhook (pay-by-bank or card) |
REFUND_STATUS | Refund-status webhook |
EXPIRED_STATUS | Payment expired (customer didn’t pay in time) |
POS_PAYMENT_STATUS | POS event — choose which event to simulate via --type |
Customising the dispatched body
| Flag | Use |
|---|---|
--orderId | Override the orderId in the dispatched payload |
--amount | Override the amount in pounds, e.g. 10.05 for £10.05 |
--paymentMethod | CARD or PAY_BY_BANK |
--status | Force a specific status (COMPLETED, AUTHORIZED, FAILED, CANCELLED, EXPIRED) |
--type | POS_PAYMENT_STATUS only — which POS event to simulate: PAYMENTS_STATUS (default) | REFUND_STATUS | EXPIRED_STATUS |
--customFields | POS_PAYMENT_STATUS only — JSON array of {value, fieldName} objects |
# Successful card payment
atoa webhooks trigger PAYMENTS_STATUS --paymentMethod CARD --status AUTHORIZED
# Failed pay-by-bank
atoa webhooks trigger PAYMENTS_STATUS --status FAILED
# Refund that failed at the issuer
atoa webhooks trigger REFUND_STATUS --status FAILED
# Custom order ID and amount
atoa webhooks trigger PAYMENTS_STATUS --orderId my-test-order --amount 50.00
# POS_PAYMENT_STATUS can simulate several POS events — pick one via --type
atoa webhooks trigger POS_PAYMENT_STATUS --type PAYMENTS_STATUS
atoa webhooks trigger POS_PAYMENT_STATUS --type REFUND_STATUS --status COMPLETED
atoa webhooks trigger POS_PAYMENT_STATUS --type EXPIRED_STATUS
atoa webhooks trigger POS_PAYMENT_STATUS --type PAYMENTS_STATUS \
--customFields '[{"value":"CUST_001","fieldName":"Customer ID"}]'
Test triggers are sandbox-only by design. If you pass --env production,
the CLI warns you and proceeds with your active profile’s sandbox key
anyway — this prevents accidentally driving fake events at your live customers.
Using the CLI in CI
atoa login needs an interactive browser, so a CI runner cannot log in itself. Instead, provision credentials on a workstation once, then hand the files to the runner:
- On a workstation:
atoa login, plusatoa keys createif the job hits SDK/data commands. - Copy
~/.atoa/auth/to a location the runner can read. - Point the runner at it with
ATOA_HOME(credentials live in$ATOA_HOME/.atoa/auth/).
# ATOA_HOME → the dir that holds .atoa/auth/. Scope to a named profile, use a stable idempotency key.
atoa --profile ci payments create \
--amount 10.00 --orderId "$RUN_ID" --customerId cust_123 --redirectUrl https://x \
--idempotencyKey "ci-payment/$RUN_ID" --dryRun
# Read-only check, machine-readable output
atoa --profile ci payments transactions --output json --status COMPLETED
# Clean up — clears the login session (server revoke is automatic); --purge-key also drops the SDK key
atoa logout --profile ci --purge-key --yes
Every command accepts --output json (the default) for machine-readable output and --dryRun to resolve the request without sending it. --yes is required on destructive commands in a non-TTY context.
Exit codes
The CLI uses POSIX-style exit codes so pipelines can branch on the failure mode:
| Code | Meaning | Trigger |
|---|---|---|
0 | Success | Command completed |
1 | Generic failure | Anything not classified below |
2 | Auth / forbidden | HTTP 401 / 403 — token invalid, revoked, or lacks permission |
3 | Validation error | HTTP 400, or client-side input rejected |
4 | Not found | HTTP 404 |
5 | Rate limited | HTTP 429 — back off and retry |
6 | Network / TLS / DNS | Couldn’t reach the server |
if ! atoa refunds create --paymentRequestId "$PR" --amount 5.00 --idempotencyKey "$RUN_ID" --yes ; then
case $? in
2) echo "Token invalid — re-login required" ;;
3) echo "Bad input — fix the payload" ;;
5) echo "Rate limited — retry with backoff" ;;
6) echo "Network blip — re-running with the same idempotency key is safe" ;;
esac
fi
Environment variables
| Variable | Purpose |
|---|---|
ATOA_HOME | Override where config + credentials live. Defaults to $HOME; credentials land in $ATOA_HOME/.atoa/auth/. Useful for Docker / ephemeral CI. |
ATOA_PROFILE | Default profile name — same as passing --profile <name> on every command. The explicit flag still wins. |
Generic HTTP verbs
When you need to hit an endpoint the CLI doesn’t yet wrap (or you want full control over the request body), use the raw verbs:
atoa get /api/payments/stores
atoa get /api/customers -d page=0 -d size=20 --pageAll
atoa post /api/webhook/merchant --data @webhook.json
atoa post /api/something -d amount=1005 -d orderId=order-001
atoa delete /api/customers/cust_123 --yes
How -d and --data work
These are two different ways to build the request body. Pick whichever fits the call:
| Form | What it means | Example |
|---|---|---|
-d key=value (repeatable) | Build the body one field at a time. Same flag can be used many times. | -d amount=1005 -d orderId=order-001 |
--data '{"key":"value"}' | Pass the entire body as an inline JSON string. | --data '{"orderId":"o-1","amount":1005}' |
--data @path/to/file.json | The @ prefix means read the body from a file on disk. | --data @webhook.json |
--data - | Read the body from stdin (pipe-in form for CI scripts). | cat body.json | atoa post /api/x --data - |
So atoa post /api/webhook/merchant --data @webhook.json reads webhook.json from the directory you ran the command in, takes its contents as the JSON request body, and POSTs it to /api/webhook/merchant. Example file:
// webhook.json
{
"url": "https://your-server.example.com/atoa-hook",
"event": "PAYMENTS_STATUS"
}
-d and --data are mutually meaningful — they both contribute to the same
body. If you pass both, --data wins and -d fields are ignored. Use one or
the other per command.
Passing nested objects and arrays
Some API fields aren’t a single string or number — they’re objects (e.g. authentication, consumerDetails) or arrays (e.g. paymentMethod, storeIds). How you pass these depends on which kind of command you’re using.
-d and --data are flags on the generic verbs only (atoa get, atoa post, atoa delete). Typed commands like atoa payments create and atoa customers create accept only their named flags — they do not accept -d key=value or --data @file.json.
On typed commands (e.g. atoa payments create)
Use the command’s own named flags. Quote complex values so the shell passes them to the CLI intact:
| Value shape | How to pass it | Concrete example |
|---|---|---|
| Object | Inline JSON, single-quoted | --consumerDetails '{"firstName":"Jane","lastName":"Doe","email":"jane@example.com"}' |
| Array (comma form) | Comma-separated string | --paymentMethod CARD,PAY_BY_BANK |
| Multi-value filters | Comma-separated string | --status COMPLETED,FAILED · --storeIds store_uk_1,store_uk_2 |
# Object value via a typed flag (bash / zsh / git-bash)
atoa payments create \
--amount 25.00 --orderId order-001 --customerId cust_123 \
--redirectUrl https://shop.example.com/return \
--consumerDetails '{"firstName":"Jane","lastName":"Doe","email":"jane@example.com","phoneCountryCode":"44","phoneNumber":"7700900000"}'
# Comma-separated array via a typed flag
atoa payments transactions --status COMPLETED,FAILED --storeIds store_uk_1,store_uk_2
PowerShell users: Windows re-parses arguments when handing them to
native .exe / .cmd programs, so the bash-style '{"key":"value"}'
doesn’t survive intact. Use backslash-escaped double quotes inside the
single quotes instead:
# Object value via a typed flag (PowerShell — note the \" escapes)
atoa payments create --amount=25.00 --orderId=order-001 --customerId=cust_123 `
--redirectUrl=https://shop.example.com/return `
--consumerDetails '{\"firstName\":\"Jane\",\"lastName\":\"Doe\",\"email\":\"jane@example.com\",\"phoneCountryCode\":\"44\",\"phoneNumber\":\"7700900000\"}'
If a value contains whitespace (e.g. "Jane Doe"), wrap the whole
backslash-escaped JSON in an extra pair of outer double-quotes so Windows
doesn’t split at the space:
atoa payments create --consumerDetails '"{\"firstName\":\"Jane\",\"lastName\":\"Doe\"}"'
bash / zsh / git-bash users — the single-quoted plain form (top of section) works directly, no escaping needed.
If a typed command doesn’t expose a flag for the field you need, fall through to the generic verbs below.
On generic verbs (e.g. atoa post /api/...)
The generic verbs accept any JSON body. Three ways to specify it:
| Value shape | How to pass it | Concrete example |
|---|---|---|
| Field by field | -d key=value (repeatable) | -d amount=1005 -d orderId=order-001 |
| Object field | -d key=<JSON string> | -d authentication='{"username":"u","password":"p"}' |
| Array field (JSON) | -d key=<JSON array> | -d paymentMethods='["CARD","PAY_BY_BANK"]' |
| Whole body inline | --data '<JSON>' | --data '{"orderId":"o-1","amount":1005}' |
| Whole body from file | --data @path/to/file.json | --data @webhook.json |
| Whole body from stdin | --data - | cat body.json | atoa post /api/x --data - |
# Object value via -d (generic verb)
atoa post /api/webhook/merchant \
-d sandboxUrl=https://example.com/hook \
-d eventTypes=PAYMENTS_STATUS \
-d authentication='{"username":"u","password":"p"}'
# Whole body from a file (generic verb)
atoa post /api/payments/process-payment --data @payment.json
Wrap JSON values in single quotes so {, ", and spaces pass through
untouched.PowerShell: same rule, but escape any inner single quote by
doubling it ('It''s urgent' → It's urgent).
These use your active profile’s credentials and respect every common flag (--env, --output, --dryRun, --verbose). Useful for:
- New endpoints that haven’t gained a typed wrapper yet
- Debugging —
--verboseprints the redacted request line so you can see exactly what was sent - CI scripts where you want explicit control over the exact body sent
Shell completion
Enable TAB-completion of commands, flags, profile names, and IDs:
bash
zsh
PowerShell
atoa completion bash >> ~/.bashrc && source ~/.bashrc
After installing, hit <TAB> mid-command:
atoa pa<TAB> → payments, payment-methods, payouts
atoa payments <TAB> → create, status, cancel, transactions
atoa profile use <TAB> → live profile names from your config
Command reference
Most commands accept these common flags (per-command --help is authoritative):
| Flag | Use |
|---|---|
--env | Override env (sandbox | production) for this single command |
--output | json | table | yaml |
--verbose | Print a redacted request log for debugging |
--dryRun | Resolve the request but don’t send it |
--yes | Skip confirmation prompts |
--profile | Operate against a specific profile |
Troubleshooting
Need Help?
Contact our team at hello@paywithatoa.co.uk or use chat support on the Dashboard.