Browse the docs

Create card

POST /v2/api-gateway/cards

Issues a card funded by account_id for cardholder_id. Both ids are required on this route — leaving either out fails validation and returns { errors: [...] } naming the missing field — and the cardholder must belong to the account's organization. card_type: PHYSICAL is only accepted if physical cards are enabled for your organization, and if your organization requires cardholder identity verification the cardholder must be approved first.

Request

Authentication: your API key in the x-api-key header.

Headers

FieldTypeDescription
Idempotency-KeystringA unique string you choose, up to 255 characters, that makes this request safe to retry. If a request times out or fails in transit, send it again with the same key: the status code and body of the first attempt are returned and the operation is not performed a second time. Stored replies — including failures — are replayed for 24 hours, after which the same key starts a fresh request. Reusing a key for a different request, or sending it while the first request is still running, returns 409. Omit the header and the request behaves exactly as before. Document upload endpoints do not accept it.

Body

FieldTypeDescription
cardholder_id requiredstring, uuidThe Moon cardholder id (id, a UUID) that will hold the card. Must belong to the same organization as account_id. Required.
account_id requiredstring, uuidUUID of the account whose balance funds the card (GET /v2/api-gateway/accounts). Required.
card_typestringCard form factor: VIRTUAL (default) or PHYSICAL. PHYSICAL is only accepted if physical cards are enabled for your organization; otherwise the request is rejected with USER_NOT_ALLOWED_PHYSICAL_CARD before the card is issued.One of: VIRTUAL, PHYSICAL
card_product_idstring, uuidAlways send it: a request without it is not turned away as a missing field, but it names no product, so it will not issue the card you intended.
metadataobjectYour own key-value data to store on this card. It is returned verbatim on every read of the card and never affects how the card is processed — use it to carry your order or customer identifier so you do not have to keep a separate mapping. Up to 50 keys, keys up to 40 characters and values up to 500 characters. Values must be strings — nested objects, arrays, numbers and booleans are rejected. On update the map you send replaces the stored map in full: omit the field to leave it unchanged, send {} to clear it, and send a key with an empty string value to remove just that key.
single_usebooleanIssue the card as single-use. The card is created with one fixed velocity control (COUNT over TOTAL, limit 1): the first authorization is approved and every later authorization is declined with AUTHORIZATION_COUNT_LIMIT_REACHED. The card is not terminated, so the clearing, a reversal or a refund for that purchase still posts. The control is returned by the velocity-control endpoints flagged fixed: true and cannot be updated or deleted (CONTROL_IS_FIXED) — terminate the card instead. Default false.
merchant_lockobjectLock the card to one merchant. The card is created with one fixed velocity control (SPEND over TOTAL, limit 0 — the "no cap" convention — carrying this entry as its only_merchants allowlist): an authorization whose merchant matches neither name nor mcc is declined with MERCHANT_NOT_ALLOWED. name is matched case-insensitively as a substring of the acquirer merchant descriptor; mcc must match exactly. Give at least one of the two; when you give both, both must match. Available only on card products that support merchant locks — list them with GET /card-products and look for supports_merchant_lock: true. Any other product is rejected with MERCHANT_LOCK_UNSUPPORTED, whose details.merchant_lock_card_product_ids lists the eligible products enabled for your organization. The control is returned by the velocity-control endpoints flagged fixed: true and cannot be updated or deleted there (CONTROL_IS_FIXED); change it with the card merchant-lock endpoints instead.
merchant_lock.namestringCase-insensitive substring of the acquirer merchant name, e.g. "TRAIN TICKETS". Trimmed before matching.
merchant_lock.mccstringExact four-digit merchant category code, e.g. "5734".
ask_firstbooleanIssue the card ask-first. The card is created with one fixed velocity control (INTENT over TOTAL): nothing is charged to it until the cardholder has approved that exact purchase. Create a purchase intent first (POST /v2/api-gateway/purchase-intents) with the merchant, the amount and the reason; the cardholder receives an email with a one-time link and approves or denies it. An authorization with no approved, unspent, unexpired intent is declined with INTENT_REQUIRED; one whose merchant or amount does not match the approved intent is declined with INTENT_MISMATCH. An approval is single-use: one intent pays for one purchase. A $0 verification is allowed on a merchant match alone and does not spend the approval. Requires a cardholder with an email address on file — the approver is the person, not the caller. The control is returned by the velocity-control endpoints flagged fixed: true and cannot be updated or deleted there (CONTROL_IS_FIXED); use the card ask-first endpoints instead. Default false — except on an account whose owner requires every card created by a connected agent to ask before each purchase, where the card is always issued ask-first and sending ask_first: false is refused with AGENT_CARDS_MUST_ASK_FIRST.

Example request

cURL

curl --request POST \
     --url 'https://api.themooncard.com/v2/api-gateway/cards' \
     --header 'x-api-key: YOUR_API_KEY' \
     --header 'content-type: application/json' \
     --data '{
       "cardholder_id": "3f5b1c2e-8d4a-4b6f-9e2a-1c7d8e9f0a1b",
       "account_id": "9b2e7d10-6c1f-4a3e-8f5d-2a4b6c8d0e1f",
       "card_type": "VIRTUAL",
       "card_product_id": "a8358b18-be77-41b7-af4d-f4e9760cba86",
       "metadata": {
         "order_id": "ORD-10294"
       },
       "single_use": true,
       "merchant_lock": {
         "mcc": "5734"
       },
       "ask_first": true
     }'

Responses

201 Success

The newly issued card, in detail form.

FieldTypeDescription
id requiredstringCard id.
balance requiredstring, nullableCurrent balance, "0.00" style string, or null.
true_balance requiredstring, nullableBalance including pending holds, or null.
expiration requiredstring, date-time, nullableExpiration as an ISO-8601 timestamp, or null when unknown.
display_expiration requiredstringExpiration formatted for display, e.g. "12/29".
available_balance requiredstring, nullableSpendable balance, or null.
terminated requiredinteger1 when the card is terminated, otherwise 0.One of: 0, 1
card_product_id requiredstringCard product this card was issued from.
pan requiredstringFull card number.
cvv requiredstring, nullableCard verification value, or null when not available.
support_token requiredstringToken to reference this card in support requests.
frozen requiredinteger1 when the card is frozen, otherwise 0.One of: 0, 1
activated requiredinteger1 when the card is activated, otherwise 0.One of: 0, 1
metadata requiredobjectThe key-value data you stored on this card, returned exactly as you sent it. An empty object when you never set any — it is never null.
card_type requiredstring, nullableCard type, e.g. "virtual" or "physical"; null when unknown.

JSON

{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "balance": "25.50",
  "true_balance": "25.50",
  "expiration": "2029-12-31T23:59:59Z",
  "display_expiration": "12/29",
  "available_balance": "25.50",
  "terminated": 0,
  "card_product_id": "a8358b18-be77-41b7-af4d-f4e9760cba86",
  "pan": "4111111111111111",
  "cvv": "123",
  "support_token": "ST-8QK2-4M7D",
  "frozen": 0,
  "activated": 1,
  "metadata": {
    "order_id": "ORD-10294"
  },
  "card_type": "VIRTUAL"
}
400 Error

Validation failed — account_id and cardholder_id are required and must be UUIDs, and a missing or malformed one returns the { errors: [...] } validation body naming it.

The body is the standard error object: see Errors.

401 Error

Missing or invalid credentials, e.g. { "message": "Invalid API Key", "code": "UNAUTHORIZED" }.

The body is the standard error object: see Errors.

403 Error

A card-issuance limit or the lifetime card cap for this card product would be exceeded, or the account owner requires every card created by a connected agent to ask before each purchase and this request asked for ask_first: false (AGENT_CARDS_MUST_ASK_FIRST). Only the account owner can change that setting; omit ask_first and the card is issued asking first.

The body is the standard error object: see Errors.

404 Error

Account, cardholder or card not found or not owned by you (ACCOUNT_NOT_FOUND, CARDHOLDER_NOT_FOUND, CARD_NOT_FOUND).

The body is the standard error object: see Errors.

409 Error

The Idempotency-Key you sent is already in use. IDEMPOTENCY_KEY_REUSED — the key was used for a different request; send a new key, or repeat the original request exactly to receive its stored response. IDEMPOTENCY_KEY_IN_PROGRESS — the first request with this key has not finished yet; retry in a few seconds.

The body is the standard error object: see Errors.

500 Error

Unexpected server error. Body is { message: "Internal server error", code: "INTERNAL_SERVER_ERROR" }.

The body is the standard error object: see Errors.

Last updated June 5, 2026