Skip to content

Issue cards

A card is issued in one call. A virtual card works as soon as it is created, wherever a card number is taken. It is your business’s own unless you give it to one of your agents. A physical card belongs to a person and is posted to them.

Every card spends from the same balance, within limits that Tuppence and its card processor enforce. Eligible spend earns cashback.

  1. Switch cards on in the dashboard, under Cards. Your business is reviewed first.
  2. On the same page, the owner adds themselves as the primary cardholder. Cards are issued in their name, as the person who answers for them.
  3. Top up the card balance by bank transferNothing to fund: the cards spend your earnings once they’ve clearedTop up the card balance by SEPA transfer to its own IBAN (below).

You don’t need an agent or a spending policy. Creating and changing cards needs a key with cards:write; reading them needs cards:read.

A restricted key can make a card stricter, never looser. With cards:write it can lower a limit or add one, freeze a card, cancel it, and change its metadata.

It can also get its agent a card for one purchase, when the request is signed as that agent and the card is:

  • for that agent only;
  • single-use (usage: "single_use" with an amount);
  • locked to one merchant (merchant);
  • on the agent’s default spending policy. A person sets that policy first, and amount can’t be more than its limits allow.

Anything else that lets a card spend more needs a person (the dashboard or the app) or a secret key:

  • any other card, and replacing or activating one;
  • raising or removing a limit;
  • changing a card’s spending policy, or an agent’s default policy;
  • unfreezing a card that a person, another key or Tuppence froze. A restricted key can unfreeze a card it froze itself.

Otherwise it gets 403 permission_denied, and nothing changes. A card an agent’s key issues is recorded as that key’s, in the audit log and on its card.created event’s request.

By default, a virtual card is your business’s own, issued to the primary cardholder, active at once, with a limit of £500.00$500.00€500.00 a day.

Terminal window
curl https://api.tuppence.ai/v1/cards \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: team-tools-card" \
-H "Content-Type: application/json" \
-d '{"limits": {"monthly": 50000}}'
import { Tuppence } from "@tuppence/node";
const tuppence = new Tuppence(process.env.TUPPENCE_SECRET_KEY);
// A virtual card, up to £500 a month.
const card = await tuppence.cards.create({ limits: { monthly: 50000 } });
import { Tuppence } from "@tuppence/node";
const tuppence = new Tuppence(process.env.TUPPENCE_SECRET_KEY);
// A virtual card, up to $500 a month.
const card = await tuppence.cards.create({ limits: { monthly: 50000 } });
import { Tuppence } from "@tuppence/node";
const tuppence = new Tuppence(process.env.TUPPENCE_SECRET_KEY);
// A virtual card, up to €500 a month.
const card = await tuppence.cards.create({ limits: { monthly: 50000 } });
import os
from tuppence import Tuppence
tuppence = Tuppence(os.environ["TUPPENCE_SECRET_KEY"])
card = tuppence.cards.create(limits={"monthly": 50000})
{
"id": "card_…",
"object": "card",
"livemode": false,
"agent": null,
"cardholder": "ich_…",
"type": "virtual",
"usage": "standing",
"status": "active",
"currency": "gbp",
"brand": "Visa",
"last4": "4242",
"limits": {
"per_authorization": null,
"daily": null,
"weekly": null,
"monthly": 50000,
"all_time": null
},
"spend_policy": null,
"merchant_lock": null,
"metadata": {}
}
{
"id": "card_…",
"object": "card",
"livemode": false,
"agent": null,
"cardholder": "ich_…",
"type": "virtual",
"usage": "standing",
"status": "active",
"currency": "usd",
"brand": "Visa",
"last4": "4242",
"limits": {
"per_authorization": null,
"daily": null,
"weekly": null,
"monthly": 50000,
"all_time": null
},
"spend_policy": null,
"merchant_lock": null,
"metadata": {}
}
{
"id": "card_…",
"object": "card",
"livemode": false,
"agent": null,
"cardholder": "ich_…",
"type": "virtual",
"usage": "standing",
"status": "active",
"currency": "eur",
"brand": "Visa",
"last4": "4242",
"limits": {
"per_authorization": null,
"daily": null,
"weekly": null,
"monthly": 50000,
"all_time": null
},
"spend_policy": null,
"merchant_lock": null,
"metadata": {}
}

The response never includes the card’s number or CVC. People see those in the dashboard or app. Where the owner allows it, a key made with card_details:read can read them to pay at a checkout: see See a card’s details.

Parameters you can set on create:

ParameterWhat it does
limitsper_authorization, daily, weekly, monthly and all_time, in minor units. Without it, the card has 500 a day in your currency
usagestanding (the default: it keeps working) or single_use: one purchase, up to amount
amountThe most a single-use card can spend, in minor units
merchantThe one merchant it can be used at: name, network_id, or both
spend_policyThe rules it follows (pol_…)
agentThe agent it is for (agt_…). Leave it out for your business’s own card
cardholderWho it is issued to (ich_…). Defaults to the primary cardholder
statusactive (the default) or inactive, to activate it later
metadataYour own keys and values

A card is issued in your account’s currency: GBP in the UK, USD in the US.

A single-use card locked to one merchant pays that merchant once, up to its amount. It’s the safest card for a one-off purchase.

const once = await tuppence.cards.create({
usage: "single_use",
amount: 8000, // £80.00
merchant: { name: "Acme Cloud" },
});
const once = await tuppence.cards.create({
usage: "single_use",
amount: 8000, // $80.00
merchant: { name: "Acme Cloud" },
});
const once = await tuppence.cards.create({
usage: "single_use",
amount: 8000, // €80.00
merchant: { name: "Acme Cloud" },
});

Name an agent to give it its own card. The card follows the agent’s default rules unless you set others, and each purchase is checked against what is usual for that agent.

const agent = await tuppence.agents.create({ name: "Research agent" });
const card = await tuppence.cards.create({ agent: agent.id, limits: { monthly: 50000 } });

A request signed by an agent (agent identity) that names no agent creates a card for that agent. An agent reads only its own cards’ numbers.

A physical card is for one person (one each). It is posted to them and activated once it arrives.

const card = await tuppence.cards.create({
type: "physical",
cardholder: "ich_…",
limits: { monthly: 100000 },
});
// Once it has arrived:
await tuppence.cards.activate(card.id);

It goes to the cardholder’s billing address unless you set shipping. The card’s shipping tracks each move, and card.shipped is sent when it is on its way.

A purchase must fit within what the cards can spend: spendable on GET /v1/issuing/balance. Where that money comes from depends on your account’s country (spends_from).

UK cards spend the card balance, kept apart from the money you take. Top it up by bank transfer from any business bank account to the card balance’s own sort code and account number. Topping up is free, and a transfer usually arrives within minutes in banking hours.

StepCall
1. See what the cards can spendGET /v1/issuing/balance: spendable, with spends_from: issuing_balance
2. Get the bank detailsGET /v1/issuing/funding_instructions: gb.account_holder_name, gb.sort_code, gb.account_number. They’re your account’s own: save them as a payee and reuse them
3. Send the transferFrom your bank. spendable rises when it lands
In test modePOST /v1/test_helpers/issuing/fund with amount: the transfer arrives, spendable at once, and issuing_funding.available is sent
Terminal window
# 1. What the cards can spend now.
curl -sS --fail-with-body https://api.tuppence.ai/v1/issuing/balance \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY"
# 2. The card balance's own sort code and account number.
curl -sS --fail-with-body https://api.tuppence.ai/v1/issuing/funding_instructions \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY"
# 3. Test mode: a £100.00 transfer arrives.
curl -sS --fail-with-body https://api.tuppence.ai/v1/test_helpers/issuing/fund \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount": 10000}'

GET /v1/issuing/funding lists the test transfers, newest first. Money still clearing can also move onto the cards at once, for the instant fee: POST /v1/issuing/topups with method: "instant_from_balance", where instant_payouts.destinations.card_balance on GET /v1/payout_settings says it’s available.

Earnings that have cleared can move onto the cards too, free: POST /v1/issuing/topups with {"method": "balance", "amount": 5000}. It’s decided like a payout (cleared money only, holds and limits apply), answers in_transit with expected_available_at set to Stripe’s date, usually the next day, and becomes available then. In live mode it’s being switched on account by account: until it’s on for yours, the answer is topup_unavailable with reason: not_enabled and a fix_url to top up by bank transfer.

A Trusted account can also top up by card at speed: instant: the card rate plus 0.99% on top, spendable within a minute of the payment. Below Trusted it’s refused with reason: tier and a fix_url to ask for higher limits.

US cards spend your earnings directly, once they’ve cleared, so there’s nothing to move. spendable is what you could pay out right now, and a purchase counts toward your daily payout limit. Earnings aren’t spendable in your account’s first seven days. To add money, top up by bank transfer: POST /v1/issuing/topups with method: bank_transfer gives your card balance’s own account and routing number. What you send is spendable as soon as it arrives, usually by the next business day, and can be paid out only once it has cleared. The fee is 0.5%, at most $5, on top. A Trusted account can also top up by card at speed: instant: the card rate plus 0.99% on top, spendable at once, and payable out only once the payment clears. method: balance is refused with already_spendable: the cards already spend your earnings.

StepCall
1. See what the cards can spendGET /v1/issuing/balance: spendable, with spends_from: payments_balance. limited_by, available_from and message say what holds it and when more clears
2. Bank detailsPOST /v1/issuing/topups with {method: bank_transfer, amount}: next_action.bank_transfer has the routing and account number and the reference; send the top-up’s total. (GET /v1/issuing/funding_instructions still answers available: false, reason: spends_earnings)
In test modeTake a payment with pm_card_bypassPending, then move the test clock seven days on. The cards can spend it
Terminal window
# 1. What the cards can spend now, and what holds it.
curl https://api.tuppence.ai/v1/issuing/balance \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY"
# 2. No bank details: available is false, reason spends_earnings.
curl https://api.tuppence.ai/v1/issuing/funding_instructions \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY"

European cards spend the card balance, kept apart from the money you take, as in the UK. They are Visa commercial cards in euros, for business use. Top the balance up by SEPA transfer from any business bank account to the card balance’s own IBAN. Topping up is free, and a transfer usually arrives the next banking day. A purchase the balance can’t cover is declined, so keep an eye on spendable. Moving earnings onto the cards, and topping up by card, aren’t offered in Europe yet.

StepCall
1. See what the cards can spendGET /v1/issuing/balance: spendable, with spends_from: issuing_balance
2. Bank detailsGET /v1/issuing/funding_instructions: type: eu_bank_transfer, and eu with account_holder_name, iban, bic and the IBAN’s country. Or POST /v1/issuing/topups with {method: bank_transfer, amount} for the same IBAN and a reference
In test modePOST /v1/test_helpers/issuing/fund with amount: the transfer arrives and issuing_funding.available is sent

Every cardholder needs a mobile number (phone_number): the card’s bank sends its 3-D Secure code by text. The 3-D Secure screens are in English, French, German, Italian or Spanish, by the cardholder’s language, and in English otherwise.

Cards issued by Stripe. Cards are issued by Stripe Technology Europe Limited, an electronic money institution authorized by the Central Bank of Ireland (firm reference number: C187865). Cards are issued under the Visa card scheme pursuant to a license from Visa Europe Limited.

Reading the balance and the bank details needs issuing_funding:read; the test helper needs test_helpers:write. Before cards are switched on, each call answers 403 issuing_not_enabled.

A card always has at least one limit. Change them at any time; null removes one.

await tuppence.cards.setLimits(card.id, { monthly: 100000, per_authorization: 5000 });

Your account also has limits: how many cards it can create and what they can spend together. A card’s limits can only be tighter. See Card limits.

A spending policy (/v1/spend_policies in the API reference) sets which kinds of merchant and which countries a card may pay, and the amount above which a person must approve a purchase first. Set one when you create the card or later. Without one, an agent’s card follows the agent’s default, and any other card is stopped only by its limits.

await tuppence.cards.update(card.id, { spend_policy: "pol_…" });
await tuppence.cards.freeze(card.id); // every purchase is declined until it is unfrozen
await tuppence.cards.unfreeze(card.id);
await tuppence.cards.cancel(card.id); // for good

Replace a lost or stolen standing card with POST /v1/cards/{id}/replace and a reason. The old card stops, and the new one keeps the holder, agent, limits and rules. A single-use card can’t be replaced: cancel it and create another. A physical card can also be replaced when damaged or expired; the old one keeps working until the new one is activated.

card.created is sent when a card is issued, and card.updated whenever its status, limits or rules change. Each carries the card. See Webhooks.

CodeHTTPMeans
parameter_invalid400A value isn’t one the API takes. param names it: a disabled agent, an agent on a physical card, a currency that isn’t your account’s
parameter_missing400A single-use card without amount, or no primary cardholder yet to issue it to (add one on the dashboard’s Cards page, or pass cardholder)
resource_missing404No such agent, cardholder or spending policy in this account and mode
resource_limit_reached400The person already has a physical card
issuing_card_limit403Over one of the account’s card limits
permission_denied403A restricted key tried to loosen a card. See what an agent’s restricted key can do

Docs assistant

Open as a page
Loading the assistant…