Quickstart
Seven calls from your terminal. At the end you will have issued, funded, partly unloaded and closed a real card, and seen every envelope and id the reference talks about.
The card you create here is a real Visa card and the balance you move is your real balance. Use a small amount. Steps 6 and 7 bring it all back.
You need a key with users:read, cards:read, cards:write and cards:pan, a funded balance (funding is confirmed with POST /funding), a mobile number you can receive SMS on, and an email address for the cardholder.
export KTAP_API_KEY="ktap_live_..."
export KTAP_BASE="https://virtual.ktap.io/api/partner/v1"1. Check your key and balance#
curl -sS "$KTAP_BASE/me" -H "X-API-Key: $KTAP_API_KEY"The figure that matters is data.float.available_usd. That, not balance_usd, is what a top-up is checked against. A 401 here means the key is wrong, revoked or suspended, and the three are indistinguishable.
2. Order a card#
curl -sS -X POST "$KTAP_BASE/cards" \
-H "X-API-Key: $KTAP_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{
"card_holder_name": "Ada Lovelace",
"phone_country_code": 1,
"phone": "5551234567",
"email": "ada@yoursite.com",
"partner_ref": "quickstart-001"
}'card_holder_name takes letters, digits, äöüÄÖÜ, space, and / - ^ . ’ ), at most 26 characters. Send the name as the user wrote it: O’Brien works as is, José converts to Jose automatically, and Müller keeps its umlaut. Only ß, Ø, Æ and non-Latin scripts are rejected; send an ASCII form for those. phone_country_code is the country calling code on its own, as an integer. phone is the subscriber number, 4 to 15 digits with no punctuation, so a leading + returns 400. email is required and goes to the card processor; we do not store it. partner_ref is your own identifier, handed back on the card and on every webhook, which is what makes reconciliation possible.
Without one, a retry after a timeout can issue a second card or move your money twice. If a call times out, retry the identical request with the same key. A new key is a second, unrelated operation. See Idempotency.
export CARD_ID="7Kd2mQ4rTz9XbW1sYh6vN0pLcE3fJa8u" # data.card.id from the response3. Put money on it#
curl -sS -X POST "$KTAP_BASE/cards/$CARD_ID/topup" \
-H "X-API-Key: $KTAP_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{"amount_usd": 25.00}'A top-up moves value at 1:1 with no fee. Amounts take at most two decimal places and are rejected, never rounded, above that.
4. Read the card back#
curl -sS "$KTAP_BASE/cards/$CARD_ID" -H "X-API-Key: $KTAP_API_KEY"The balance here is read live from the card processor. An empty card reads 0; null means the read itself failed, so treat null as unknown and retry rather than as empty. For many cards at once use POST /cards/balances, which answers instantly at any fleet size.
5. Show the card number#
curl -sS "$KTAP_BASE/cards/$CARD_ID/pan" -H "X-API-Key: $KTAP_API_KEY"Card numbers are never returned as data. You get a URL valid for 600 seconds and put it in an iframe, which keeps the number off your servers and out of your logs. If your app cannot render an iframe, POST /cards/{cardId}/pan-token mints a single-use URL for the cardholder’s own device to fetch instead. Card details has both.
6. Pull money back off#
curl -sS -X POST "$KTAP_BASE/cards/$CARD_ID/unload" \
-H "X-API-Key: $KTAP_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{"amount_usd": 10.00}'The exact inverse of a top-up: money comes off the card and back to your balance at 1:1 with no fee, and the card stays open. Asking for more than the card holds is refused with 402 and nothing moves. The Idempotency-Key rule is the same as on a top-up, because the retry risk is the same money twice.
7. Clean up#
curl -sS -X POST "$KTAP_BASE/cards/$CARD_ID/close" -H "X-API-Key: $KTAP_API_KEY"Closing sweeps the remaining balance back to you and stops the card permanently. It is idempotent on the card id, so no key is needed. The monthly fee for the month the card existed is still charged, with no proration.
Next#
Read Essentials once before you build properly: it covers the response envelope, the error codes worth branching on, idempotency, paging and rate limits. Then register a webhook so you stop polling. At fleet scale the calls that matter next are POST /cards/balances for balances, POST /cards/{cardId}/unload to pull money back without closing a card, and GET /events to backfill anything your consumer missed.