Browse the docs

Issuing Your First Card

This page will help you get started with Moon's Card Issuing API

This walkthrough issues a virtual card in three requests: list the card products you can issue from, create a card for one of your customers, then read the card back. It uses the V1 API; the V2 equivalents are listed at the end.

Every request carries your API key in the x-api-key header. See Authentication and Environments.

1. List the card products you can issue

Card products are the templates cards are issued from. The list contains only the products enabled for your organization, one page at a time: current_page (default 1) and per_page (default 10) choose the page, and a pagination object beside card_products tells you how many products there are and whether another page follows.

cURL

curl --request GET \
     --url 'https://api.themooncard.com/v1/api-gateway/card/card-products?current_page=1&per_page=10' \
     --header 'accept: application/json' \
     --header 'x-api-key: YOUR_API_KEY'

Take the id of the product you want to issue from; it is the card_product_id in the next request. minimum_value / maximum_value are the smallest and largest amounts that can be loaded onto a card of that product (null when unrestricted); denominations lists fixed load amounts when the product only allows those.

Keep requesting pagination.next_page until it is null to walk the whole catalogue. A page past the end returns an empty card_products list, still with the correct total.

Reference: List card products.

2. Issue a card

Create the card with POST /v1/api-gateway/card/{card_product_id}. The request body takes:

FieldRequiredDescription
end_customer_idyesYour own identifier for the customer who will hold the card. Must not be blank. If no cardholder with this id exists in your organization yet, one is created for you.
amountnoInitial balance to load onto the card, in major units (25.50). Taken from your prepaid balance. Omit it, or send 0, to issue the card unfunded.
card_currencynoUSD (default) or MXN. Must match the currency of your prepaid balance.
card_typenoVIRTUAL (default) or PHYSICAL.

What the card costs you

Your Prepaid balance — the balance on your account that is not yet allocated to any card — pays for the card. Issuing one debits it by amount, plus any card-creation fee agreed for that product, less any discount on your account. The fee is charged once, when the card is created; where a fee is a flat amount or has a floor, it applies even when you issue the card unfunded. Topping a card up later with POST /card/{card_id}/add-balance costs only the amount you add.

Issuing the card, charging for it and loading it happen together. If any part fails, none of it is applied: no card is issued and nothing is debited.

Your organization must already hold a prepaid balance in the currency you ask for, whether or not you send an amount — see Adding a balance for how to fund it.

Before the card is issued

Every check below runs before anything is created, so a rejected request issues no card and leaves no cardholder behind:

  • card_type: "PHYSICAL" requires physical cards to be enabled for your organization;
  • if your organization requires cardholder identity verification, a cardholder that already exists for this end_customer_id must be approved first. A first-time end_customer_id is not gated — there is nothing to verify yet;
  • card_currency must match the currency of your prepaid balance;
  • a positive amount requires a product that accepts balance loads, must clear the minimum first load (5 USD in production, 1 USD in Sandbox), and must be covered — together with the fee — by your available prepaid balance.

cURL

curl --request POST \
     --url https://api.themooncard.com/v1/api-gateway/card/8f1c611d-098d-4f61-b106-f7b6d344b1ae \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --header 'x-api-key: YOUR_API_KEY' \
     --data '{
        "end_customer_id": "customer_123",
        "card_type": "VIRTUAL"
      }'

The response is 201 Created with the card object itself (no wrapper), in the same shape as GET /v1/api-gateway/card/{card_id}:

The request above sent no amount, so the card starts empty. Had it sent one, balance and available_balance on this response would already show the loaded amount.

A few things to know about this object:

  • id is the card id you use on every other card endpoint. Store it.
  • expiration is an ISO-8601 UTC timestamp; display_expiration is the MM/YY form to print on the card.
  • pan and cvv are the full card credentials — treat them as sensitive data.

Reference: Create card by card product id.

Errors you may see on the first call

Errors return { "message", "code" }; validation failures return { "errors": [ { "msg", "param", "location" } ] }. Branch on the HTTP status first, then on code. See Errors.

StatuscodeWhen
400— (errors[])end_customer_id is missing from the body: {"errors":[{"msg":"Invalid value","param":"end_customer_id","location":"body"}]}. Also returned when amount is negative or not a number, or card_currency is neither USD nor MXN.
400END_CUSTOMER_ID_REQUIREDend_customer_id is present but blank ("" or only spaces).
400CARD_PRODUCT_NOT_SUPPORTEDThe card_product_id in the path is not enabled for your organization, or you sent an amount for a product that does not accept balance loads. Use an id from step 1.
400USER_NOT_ALLOWED_PHYSICAL_CARDcard_type: "PHYSICAL" was sent and physical cards are not enabled for your organization.
400CARDHOLDER_KYC_NOT_APPROVEDYour organization requires cardholder identity verification and the existing cardholder for this end_customer_id is not approved.
400USER_BALANCE_NOT_FOUNDYour organization has no prepaid balance yet. Fund it first — see Adding a balance.
400INVALID_CARD_CURRENCYcard_currency differs from the currency of your prepaid balance.
400AMOUNT_LESS_THAN_MINIMUMamount is below the minimum first load — 5 USD in production, 1 USD in Sandbox. Does not apply to reloadable products.
400INSUFFICIENT_BALANCEYour available prepaid balance does not cover amount plus the card-creation fee. message states both figures.
401UNAUTHORIZEDMissing or invalid x-api-key — or, with a valid key and the message Please contact Moon support., card issuance is not enabled for your account yet.
403HTTP_EXCEPTIONA card-issuance limit for your organization or this product has been reached; the message explains which one. Contact support to raise it.
404CARDHOLDER_NOT_FOUNDThe cardholder for end_customer_id could not be resolved in your organization.

JSON

{
  "message": "Card product 9798d4d8-c1ea-4a52-820f-ba98af5e478f does not support this operation",
  "code": "CARD_PRODUCT_NOT_SUPPORTED"
}

3. Read the card back

Fetch the card at any time with its id.

cURL

curl --request GET \
     --url https://api.themooncard.com/v1/api-gateway/card/8d2cd518-5ef2-4280-91e8-63b1a0e672e7 \
     --header 'accept: application/json' \
     --header 'x-api-key: YOUR_API_KEY'

An unknown id, or a card that belongs to another organization, returns 404 with code: "CARD_NOT_FOUND".

Reference: Get card by id.

Next: fund the card

The card issued above holds 0.00, because the request sent no amount. To load money onto it — or to build up the prepaid balance that both an initial amount and later top-ups are paid from — continue with Adding a balance.

In V2

The V2 API models the same flow with explicit organizations, accounts and cardholders: you create the cardholder and the account first, fund the account, then issue the card against it. Quickstart (V2) walks through it request by request.

See API versions for how the two versions relate.

Last updated August 17, 2026