Metadata
Attach your own key-value data to a Moon resource and read it back verbatim.
metadata is your own key-value data, stored on a Moon resource and handed back to you verbatim on every read.
Its purpose is to remove the lookup table you would otherwise keep. Store your order id on the card you issue for it, and every later read of that card — including the transaction webhook — tells you which order it belongs to, without a second system to reconcile.
JSON
{
"cardholder_id": "3f8c6f2e-9c41-4a5f-9c73-0c9f4c0b7a11",
"account_id": "6c1c9a37-9c1b-4f6c-9e79-8f8a2f0d5f31",
"card_product_id": "8f1c611d-098d-4f61-b106-f7b6d344b1ae",
"metadata": {
"order_id": "ORD-90210",
"customer_ref": "acct_2291",
"campaign": "summer-launch"
}
}Where you can set it
| Resource | Set when creating | Change later |
|---|---|---|
| Card | yes | yes, PATCH /cards/{card_ |
| Cardholder | yes | yes, PATCH /cardholders/{id} |
| Velocity control | yes | yes, PATCH /controls/velocity/{id} |
| Account | yes | no |
Creating a card works on both surfaces: POST /v1/api-gateway/card/{card_ and POST /cards.
Limits
| Keys per resource | 50 |
| Key length | 40 characters, and never empty |
| Value length | 500 characters |
| Value type | String |
Values must be strings. A nested object, an array, a number or a boolean is rejected with a validation 400 — send "1500", not 1500, and serialize anything structured yourself.
Changing it
The map you send replaces the stored map in full. It is not merged key by key, which is the one thing worth reading twice:
| You send | Result |
|---|---|
| Nothing — field omitted | The stored map is left exactly as it was |
{"order_ | The map becomes only order_; every other key is gone |
{} | The map is cleared |
{"order_ | order_ is removed, and any key you did not name is also gone |
To add a key without losing the others, read the resource, add your key to the map you got back, and send the whole map:
cURL
curl --request PATCH \
--url https://api.themooncard.com/v2/api-gateway/cards/24c96f22-0638-44ab-821f-336f9fde8f05 \
--header 'content-type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
"metadata": {
"order_id": "ORD-90210",
"customer_ref": "acct_2291",
"refund_case": "RC-77"
}
}'An empty-string value is how you delete a single key, and it is subject to the same rule: the keys you leave out still disappear.
Reading it
metadata is always an object on a response, never null and never absent. A resource you never set it on reads as {}, including resources created before the field existed — so card.metadata.order_ is safe to read without checking whether the map is there first.
Keys come back exactly as you sent them: same spelling, same case, same order-independent set.
In webhooks
The CARD_ and CARD_ events carry the card's map at data.card.metadata, so a transaction can be matched to your own order without calling back for the card:
JSON
{
"type": "CARD_TRANSACTION",
"data": {
"card": {
"public_id": "24c96f22-0638-44ab-821f-336f9fde8f05",
"metadata": { "order_id": "ORD-90210" }
}
}
}The field is always present on those events. If it arrives empty for a card you know carries keys, read the card to get the current map.
What not to put in it
metadata is stored as you send it and is returned in full to anyone holding your API key, and it appears in the webhook payloads above. Keep cardholder personal data, card numbers, credentials and anything you would not want echoed back out of it — use the fields the API provides for those, which are handled accordingly.
Related
- Data formats and IDs — types and identifier rules for the rest of the request.
- Errors — the validation envelope you get when a key or value is out of bounds.
Last updated August 24, 2026