KTAP Cards API

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.

This spends real money

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.

Set up your shell
export KTAP_API_KEY="ktap_live_..."
export KTAP_BASE="https://virtual.ktap.io/api/partner/v1"

1. Check your key and balance#

bash
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#

bash
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.

Always send an Idempotency-Key here, on a top-up and on an unload

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.

bash
export CARD_ID="7Kd2mQ4rTz9XbW1sYh6vN0pLcE3fJa8u"   # data.card.id from the response

3. Put money on it#

bash
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#

bash
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#

bash
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#

bash
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#

bash
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.

Quickstart · KTAP Cards API