Corgi BankDocumentation
OpenAPI

Issue a debit card

This page starts with an open consumer checking account and ends with a cleared purchase and a declined one, all in the sandbox. Each step is one request.

Before you start

  • A verified customer who is a person, and an open consumer checking account that customer owns, with money in it. See Getting started.
  • A staff token that carries cards:write and accounts:read, as the card_operations staff role does, and an authentication within the last five minutes. Card management, the two simulation calls and settlement all check it. A service token from the client-credentials grant is refused with 403 forbidden, and an older staff token with step_up_required.
  • In the local sandbox, start it with -staff-fixtures. The credentials file then holds cards_staff_token, which lasts five minutes. POST /sandbox/staff-tokens, with the file's staff_fixture_secret as the bearer, mints a fresh set.

The card networks and the issuer-processor are simulators. The two /simulate/cards endpoints play them and run the same decision and clearing code the processor calls.

1. Issue the card

POST /cards needs the account, the person who will hold the card and the network. The answer is 201 with status inactive. Keep the card's id.

Issue
curl -X POST 'https://api.dev.bank.corgi.com/cards' \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"account_id": "acct_2tVh8nqLxq4GbDe0K1F6S9zRcWm",
       "cardholder_customer_id": "cust_2tVh8nqLxq4GbDe0K1F6S9zRcWm",
       "network": "visa"}'

2. Activate it

An inactive card declines every purchase with code 78. POST /cards/{id}/activate takes no body and returns the card as active.

Activate
curl -X POST 'https://api.dev.bank.corgi.com/cards/card_2tVh8nqLxq4GbDe0K1F6S9zRcWm/activate' \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

3. Set controls

PUT /cards/{id}/controls replaces the whole set. This request blocks merchant category 7995, betting and casinos, and keeps the defaults for the rest. Leave a channel out and it is off. See Controls.

Controls
curl -X PUT 'https://api.dev.bank.corgi.com/cards/card_2tVh8nqLxq4GbDe0K1F6S9zRcWm/controls' \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"blocked_mccs": ["7995"],
       "channels": {"pos": true, "ecommerce": true, "atm": true, "contactless": true},
       "per_transaction_limit_amount": 500000,
       "daily_limit_amount": 1000000}'

4. Make a purchase

POST /simulate/cards/purchase authorizes a purchase on the card by its card_id. Without a network_ref it makes one up. The answer is the decision: approved, code 00, and the authorization_id.

A coffee for 6.50
curl -X POST 'https://api.dev.bank.corgi.com/simulate/cards/purchase' \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"card_id": "card_2tVh8nqLxq4GbDe0K1F6S9zRcWm",
       "amount": 650, "currency_code": "USD",
       "merchant_name": "BLUE BOTTLE", "mcc": "5812",
       "country": "US", "channel": "pos"}'

5. See the hold

GET /authorizations/{id} shows the authorization as authorized, with the hold_id of the ledger hold it placed and the expires_at after which an uncleared purchase lapses, seven days for this merchant category. On GET /accounts/{id}, balances.holding_amount is up by 650 and available_amount is down by 650. ledger_amount has not moved.

Read the authorization
curl 'https://api.dev.bank.corgi.com/authorizations/auth_2tVh8nqLxq4GbDe0K1F6S9zRcWm' \
  -H "Authorization: Bearer $TOKEN"

6. Clear it

POST /simulate/cards/clearing presents the clearing as the network would. The authorization comes back cleared with cleared_amount and the journal_id of the posting. That journal debits the account, credits card settlement payable and releases the hold, so ledger_amount is now down by 650 and holding_amount is back where it was. The events are cards.authorization.approved and then cards.authorization.cleared.

Clear
curl -X POST 'https://api.dev.bank.corgi.com/simulate/cards/clearing' \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"authorization_id": "auth_2tVh8nqLxq4GbDe0K1F6S9zRcWm", "amount": 650}'

7. See a decline

Send a purchase at a casino. The card's controls refuse it: decision declined, reason control_declined, code 57. The HTTP status is still 200, no hold is placed, and cards.authorization.declined is published.

A blocked category
curl -X POST 'https://api.dev.bank.corgi.com/simulate/cards/purchase' \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"card_id": "card_2tVh8nqLxq4GbDe0K1F6S9zRcWm",
       "amount": 5000, "currency_code": "USD",
       "merchant_name": "LUCKY 7", "mcc": "7995",
       "country": "US", "channel": "pos"}'

8. Settle the day

The cleared purchase is owed to the network until the day's settlement pays it. POST /cards/settlements, with today's business_date and the card's network, answers with the count, the cleared_amount and the journal_id that moved the money from card settlement payable to the bank's master account. See Authorizations and clearing.

Settle
curl -X POST 'https://api.dev.bank.corgi.com/cards/settlements' \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"business_date": "2026-09-17", "network": "visa"}'

to move to open esc to close