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


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 requiredCommands
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:

KeyAllowed valuesNotes
envsandbox | productionSets 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.


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

EventDescription
PAYMENTS_STATUSPayment-status webhook (pay-by-bank or card)
REFUND_STATUSRefund-status webhook
EXPIRED_STATUSPayment expired (customer didn’t pay in time)
POS_PAYMENT_STATUSPOS event — choose which event to simulate via --type

Customising the dispatched body

FlagUse
--orderIdOverride the orderId in the dispatched payload
--amountOverride the amount in pounds, e.g. 10.05 for £10.05
--paymentMethodCARD or PAY_BY_BANK
--statusForce a specific status (COMPLETED, AUTHORIZED, FAILED, CANCELLED, EXPIRED)
--typePOS_PAYMENT_STATUS only — which POS event to simulate: PAYMENTS_STATUS (default) | REFUND_STATUS | EXPIRED_STATUS
--customFieldsPOS_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:

  1. On a workstation: atoa login, plus atoa keys create if the job hits SDK/data commands.
  2. Copy ~/.atoa/auth/ to a location the runner can read.
  3. 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:

CodeMeaningTrigger
0SuccessCommand completed
1Generic failureAnything not classified below
2Auth / forbiddenHTTP 401 / 403 — token invalid, revoked, or lacks permission
3Validation errorHTTP 400, or client-side input rejected
4Not foundHTTP 404
5Rate limitedHTTP 429 — back off and retry
6Network / TLS / DNSCouldn’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

VariablePurpose
ATOA_HOMEOverride where config + credentials live. Defaults to $HOME; credentials land in $ATOA_HOME/.atoa/auth/. Useful for Docker / ephemeral CI.
ATOA_PROFILEDefault 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:

FormWhat it meansExample
-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.jsonThe @ 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 shapeHow to pass itConcrete example
ObjectInline 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 filtersComma-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 shapeHow to pass itConcrete 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--verbose prints 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):

FlagUse
--envOverride env (sandbox | production) for this single command
--outputjson | table | yaml
--verbosePrint a redacted request log for debugging
--dryRunResolve the request but don’t send it
--yesSkip confirmation prompts
--profileOperate against a specific profile

Troubleshooting


Need Help?

Contact our team at hello@paywithatoa.co.uk or use chat support on the Dashboard.