| Method | Path | Does |
|---|---|---|
| POST | /v1/api/giftcards | Issues a card |
| GET | /v1/api/giftcards | Lists the business's cards |
| GET | /v1/api/giftcards/{code} | Looks up a card |
| POST | /v1/api/giftcards/{code}/redeem | Redeems an amount |
| POST | /v1/api/redemptions/{id}/reverse | Reverses a redemption |
| POST | /v1/api/giftcards/{code}/resend | Sends the card again |
| POST | /v1/api/giftcards/{code}/cancel | Cancels the card |
| POST | /v1/api/orders/{id}/refund | Refunds a paid order |
Cancel cannot be reversed — there is no reactivation, because a card that was killed and later quietly brought back is an obligation nobody has agreed to. Refund is the only endpoint that moves money: Stripe sends the amount back to the card that paid, and nowhere else.
Try it here on the page
The console below runs the same schemas and the same redemption rule as the API, against a card that exists only in your browser. Edit the body, and see what status your own call would get.
Console
- Card
- KUV-DLT2-9GPW
- Balance
- 500,00 kr.
- Status
- active
Request
GET /v1/api/giftcards/KUV-DLT2-9GPW
Authorization: Bearer pk_test_…
No body. The code is in the path.
Response
200 OK
{
"giftCard": {
"id": "gc_3n8qk2vh",
"code": "KUV-DLT2-9GPW",
"orderId": null,
"status": "active",
"initialOre": 50000,
"balanceOre": 50000,
"expiresAt": "2029-01-15T12:00:00.000Z",
"merchantSlug": "cafe-noir",
"origin": "b2b",
"reference": "Faktura 2026-114",
"punchValueOre": null,
"noCashClaim": false,
"productName": null
}
}The balance is computed from the entries, not read off a field — which is why it always agrees with the redemptions.
Response 200 OK. The balance is computed from the entries, not read off a field — which is why it always agrees with the redemptions.
The console doesn't call out to the network — it runs the same schemas and the same redemption rule as the API, against a card that only exists in your browser. Status codes and error messages are therefore the ones your own integration gets.
Idempotency
Issue and redeem can both carry a key that makes the call safe to repeat. The same key twice answers with the result of the FIRST time — same card, same posting — instead of issuing or debiting twice. The two verbs do not detect the same thing, though: on REDEEM we answer 409 idempotency_key_conflict if the key was already used on a different card or on a payout, instead of going wrong. On ISSUE we do not compare the call — if you reuse a key with a different amount, you get 201 and the FIRST card's data back, with no error. Use one key per card.
The key can be in three places. We read idempotencyKey in the body first, and otherwise the Idempotency-Key header and then X-Request-Guid. The body wins when there is both.
| Where | Name |
|---|---|
| Body | idempotencyKey — the explicit choice, and the one that wins. |
| Header | Idempotency-Key — the name Stripe has made standard. |
| Header | X-Request-Guid — so an integration moving from Lifepeaks works without being rewritten. |
Issue a card
Issues a card outside the payment flow — sold from your own system, invoiced to a company, or given on the house.
| Field | Type | Required |
|---|---|---|
| amountOre | integer | Required. Amount in øre, at least 1. |
| validityMonths | integer | Optional. At least 36 — shorter ones are rejected. |
| origin | sold | b2b | comp | Required. See below. |
| reference | text | Optional. Your own note, e.g. an invoice number. At most 200 characters. |
| idempotencyKey | text | Optional, but recommended. The same key twice issues only one card. Can also be sent as a header — see Idempotency. |
origin is required here, whereas it is optional in the dashboard. A system issues with no one watching, and the choice follows the card for the rest of its life: sold means a customer has paid for it, and then the card carries the customer's right to have the remaining balance paid out in cash. b2b and comp do not.
curl -X POST https://api.kuvert.dk/v1/api/giftcards \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"amountOre": 50000,
"origin": "b2b",
"reference": "Faktura 2026-114"
}'Responds 201 with the card under giftCard. The amount is in øre — 50000 is 500.00 kr.
List cards
curl https://api.kuvert.dk/v1/api/giftcards \
-H "Authorization: Bearer pk_live_..."Responds 200 with the business's cards under giftCards.
Look up a card
curl https://api.kuvert.dk/v1/api/giftcards/KUV-XXXX-XXXX \
-H "Authorization: Bearer pk_live_..."Responds 200 with the card under giftCard, or 404 if the code is not found on your business.
Redeem
Debits an amount from the card's balance. The code is in the path, the amount in the body.
| Field | Type | Required |
|---|---|---|
| amountOre | integer | Required. Amount in øre, at least 1. |
| location | text | Optional. Where it happened, e.g. "Vesterbro". At most 120 characters. |
| idempotencyKey | text | Optional, but recommended. The same key twice debits only once. Can also be sent as a header — see Idempotency. |
curl -X POST https://api.kuvert.dk/v1/api/giftcards/KUV-XXXX-XXXX/redeem \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"amountOre": 6400,
"location": "Vesterbro",
"idempotencyKey": "pos-terminal-3-1753440000"
}'Responds 200 with the result of the redemption:
{
"id": "red_8f21c0b4",
"code": "KUV-XXXX-XXXX",
"amountOre": 6400,
"remainingBalanceOre": 43600,
"status": "active"
}Save the id. It is the posting, and it is the only thing that can reverse this specific redemption.
status is the card's state after redemption — one of active, redeemed, expired, void, disputed. If the balance is used up, it changes to redeemed.
If it does not go through, the response is one of these. They are not all the same for the till: some are something the guest needs to know, and some are something the business needs to be called about.
| Status | Means |
|---|---|
| 400 invalid_redeem_request | The body does not match what redemption requires — typically an amount that is not a whole number of øre, or that is zero. |
| 400 not_a_gift_card | The code in the path is a ticket code (TKT-…), not a gift card. |
| 404 gift_card_not_found | The code is not found on your business. |
| 409 card_inactive | The card is not active: used up, cancelled, set to expired or frozen under a dispute. It does not clear on its own — the business must be contacted. |
| 409 redeem_too_soon | The business has set a redemption delay, and it has not expired yet. The card is not wrong; it just cannot be used yet. Try again later. |
| 409 idempotency_key_conflict | The key is already used on a different card — or on a payout of the same card. See Idempotency. |
| 410 gift_card_expired | The card still shows as active, but the validity date has passed. Unlike card_inactive, it is time and not a decision that has closed the card. |
| 422 insufficient_balance | The amount is more than the balance. We never debit a partial amount ourselves — look up the card, debit the balance, and take the rest another way. |
| 422 invalid_amount | The redemption rule's own answer to an invalid amount. The schema normally catches it already as 400 invalid_redeem_request, so a client should be able to read both. |
| 422 punch_amount_not_whole | The card is a klippekort and the amount is not a whole number of clips. There is nothing wrong with the number — it is the wrong kind of card for it. Look the card up, read punchValueOre, and send that value or a multiple of it. A klippekort left holding less than one clip is money neither the till nor the customer can reach. |
Reverse a redemption
Puts the amount back on the card. It happens as a new posting with a negative amount that points to the one it reverses — nothing is deleted, and both lines stay in the card's history. If the card was used up entirely, it becomes active again.
curl -X POST https://api.kuvert.dk/v1/api/redemptions/red_8f21c0b4/reverse \
-H "Authorization: Bearer pk_live_..."Responds 200 with the posting that put the amount back:
{
"id": "red_2c77af90",
"code": "KUV-XXXX-XXXX",
"amountOre": -6400,
"remainingBalanceOre": 50000,
"status": "active"
}| Status | Means |
|---|---|
| 404 redemption_not_found | The posting is not found on your business. |
| 409 already_reversed | It has been reversed before. Once, never twice. |
| 409 not_reversible | The posting is itself a reversal or a cash payout. |
| 409 card_inactive | The card is cancelled or frozen under a dispute. |
| 409 order_refunded | The order behind the card has been refunded. The buyer already got the money back, so a reversal would give them the value twice. |