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:writeandaccounts:read, as thecard_operationsstaff 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 with403forbidden, and an older staff token withstep_up_required. - In the local sandbox, start it with
-staff-fixtures. The credentials file then holdscards_staff_token, which lasts five minutes.POST /sandbox/staff-tokens, with the file'sstaff_fixture_secretas 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.
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.
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.
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.
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.
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.
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.
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.
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"}'