Gift Cards API
Customers buy gift cards online and redeem them against bookings. A paid gift card becomes a voucher with a code in the form GIFT-XXXX-XXXX. The balance goes down as it is used, so one card can pay for several bookings.
Gift card products come in two kinds:
- Fixed value: for example a €50 card.
- Custom range: the customer picks an amount between a minimum and a maximum.
A product can also allow a paid validity extension: before (or shortly after) the card expires, the holder pays a fee to extend it by a set number of months.
All public endpoints need the x-tenant-id header. Amounts on products and purchases are in euros. Payment amounts returned for Stripe and all extension prices are in cents.
Public endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/gift-cards/products | List active gift card products |
| GET | /api/v1/gift-cards/products/{id} | Get one product |
| POST | /api/v1/gift-cards/purchase | Start a purchase |
| POST | /api/v1/gift-cards/purchase/{id}/payment/stripe | Create a Stripe payment for a purchase |
| POST | /api/v1/gift-cards/purchase/{id}/payment/paytrail | Create a Paytrail payment for a purchase |
| GET | /api/v1/gift-cards/purchase/{id} | Get purchase status (and the code once paid) |
| GET | /api/v1/gift-cards/balance/{code} | Check a card's balance |
| GET | /api/v1/gift-cards/{code}/extension-eligibility | Check whether a card can be extended |
| POST | /api/v1/gift-cards/{code}/extension | Start a paid extension |
| POST | /api/v1/gift-cards/extension/{id}/payment/stripe | Create a Stripe payment for an extension |
| POST | /api/v1/gift-cards/extension/{id}/payment/paytrail | Create a Paytrail payment for an extension |
| GET | /api/v1/gift-cards/extension/{id} | Get extension status |
List products
GET /api/v1/gift-cards/products returns an array of active products. Pass ?siteId= to limit it to one site.
[
{
"id": "gcp_abc123",
"name": "€50 Gift Card",
"description": "Perfect for any occasion",
"valueType": "fixed",
"fixedValue": 50,
"validityDays": 365,
"extensionEnabled": true,
"imageUrl": "https://example.com/gift-card.jpg",
"displayOrder": 0,
"isActive": true
},
{
"id": "gcp_xyz789",
"name": "Choose your amount",
"valueType": "custom_range",
"minValue": 10,
"maxValue": 500,
"suggestedValues": [25, 50, 100, 200],
"validityDays": 730,
"extensionEnabled": false,
"isActive": true
}
]
The example is shortened. Products also carry extensionPriceCents, extensionMonths, maxExtensions, extensionGraceDays, extensionGracePriceCents, siteId, productIds, metadata, createdAt and updatedAt.
Buy a gift card
- Start the purchase with
POST /api/v1/gift-cards/purchase. - Pay with Stripe or Paytrail (see below).
- Poll
GET /api/v1/gift-cards/purchase/{id}untilstatusispaidordelivered. The response then includesvoucherCodeandvoucherExpiresAt.
The gift card email goes to the recipient (or the purchaser if there is no recipient) right after payment, or on deliveryDate if you set one.
curl -X POST https://api.sessiq.com/api/v1/gift-cards/purchase \
-H "Content-Type: application/json" \
-H "x-tenant-id: {tenantId}" \
-d '{
"giftCardProductId": "gcp_xyz789",
"amount": 75,
"purchaserEmail": "buyer@example.com",
"purchaserName": "Maija Meikäläinen",
"recipientEmail": "friend@example.com",
"recipientName": "Matti",
"personalMessage": "Happy birthday!",
"deliveryDate": "2026-12-24T08:00:00.000Z"
}'
| Field | Required | Description |
|---|---|---|
giftCardProductId | Yes | Gift card product ID |
amount | For custom_range | Amount in euros. Ignored for fixed-value products |
purchaserEmail | Yes | Buyer's email |
purchaserName | No | Buyer's name |
purchaserPhone | No | Buyer's phone |
recipientEmail | No | Send the card to someone else |
recipientName | No | Recipient's name |
personalMessage | No | Message shown in the gift email |
deliveryDate | No | ISO date to send the gift email later |
deliveryMethod | No | email (default) or post |
postalAddress | For post | { name, streetAddress, postalCode, city }, Finland, 5-digit postcode |
postBonusCard | No | With post: post the bonus card in the same envelope (only when a bonus applies) |
Delivery by post. GET /api/v1/gift-cards/store-config (public, x-tenant-id) returns { "postalDelivery": { "available", "postageAmount", "postingDays", "lastOrderDate" } }. Send deliveryMethod: "post" only while available is true; otherwise the purchase is rejected with 400 POSTAL_DELIVERY_UNAVAILABLE. Postage comes from the venue's settings and is charged on top of the card value: the response has amount (card value), postageAmount and totalAmount. post can't be combined with deliveryDate, and recipientEmail becomes optional. The purchase status response includes deliveryMethod.
Response:
{
"purchaseId": "gcpur_abc123",
"amount": 75,
"currency": "EUR",
"status": "pending",
"paymentOptions": { "stripe": true, "paytrail": true }
}
Pay for a purchase
Stripe. POST /api/v1/gift-cards/purchase/{id}/payment/stripe (no body) returns:
{
"clientSecret": "pi_xxx_secret_yyy",
"paymentIntentId": "pi_xxx",
"amount": 7500,
"currency": "eur",
"publishableKey": "pk_live_xxx"
}
Initialise Stripe.js with the returned publishableKey, not a key of your own. The PaymentIntent belongs to the venue's Stripe account, so a different key cannot confirm it. Then confirm the payment with clientSecret.
Paytrail. POST /api/v1/gift-cards/purchase/{id}/payment/paytrail needs an x-return-url header with your site's base URL. It returns redirectUrl, paymentId and providerPaymentId. Send the customer to redirectUrl. After payment, Paytrail sends them back to {x-return-url}/gift-cards/payment/success?purchaseId={id} (or /gift-cards/payment/cancel?...), so your site needs pages at those paths.
The purchase is marked paid when the provider tells Sessiq the payment succeeded. See Webhooks for how provider callbacks work.
Purchase status
GET /api/v1/gift-cards/purchase/{id}:
{
"purchaseId": "gcpur_abc123",
"status": "delivered",
"paymentStatus": "succeeded",
"voucherCode": "GIFT-ABCD-1234",
"voucherExpiresAt": "2027-12-24T00:00:00.000Z",
"deliveryDate": "2026-12-24T08:00:00.000Z"
}
| Status | Meaning |
|---|---|
pending | Waiting for payment |
payment_failed | Payment failed; failureReason is set |
paid | Paid; the voucher code exists |
delivered | The gift card email has been sent |
cancelled | Cancelled |
Check a balance
GET /api/v1/gift-cards/balance/{code}:
{
"code": "GIFT-ABCD-1234",
"originalValue": 100,
"currentBalance": 75.5,
"currency": "EUR",
"expiresAt": "2027-12-24T00:00:00.000Z",
"isActive": true,
"isRedeemed": false
}
Unknown codes return 404.
Paid validity extension
When the product has extensions enabled, the card holder can pay to push the expiry date forward. The flow uses the card code, not a login.
-
Check eligibility.
GET /api/v1/gift-cards/{code}/extension-eligibility{
"code": "GIFT-ABCD-1234",
"eligible": true,
"expiresAt": "2026-10-15T00:00:00.000Z",
"priceCents": 500,
"currency": "EUR",
"months": 6,
"isGracePeriod": false,
"remainingExtensions": 2
}When
eligibleisfalse,reasonis one ofextension_not_enabled,not_a_gift_card,imported_card(an imported card while the tenant has not enabled extension for imported cards),inactive,fully_redeemed,no_expiry,misconfigured,extension_cap_reachedorpast_grace_window.A card that has already expired can still be extended during the product's grace window (
extensionGraceDays). ThenisGracePeriodistrueand the grace price applies. -
Start the extension.
POST /api/v1/gift-cards/{code}/extension(no body) returnsextensionId,priceCents,currency,months,isGracePeriod,status: "pending"andpaymentOptions. -
Pay.
POST /api/v1/gift-cards/extension/{id}/payment/stripeorPOST /api/v1/gift-cards/extension/{id}/payment/paytrail. These work exactly like the purchase payment endpoints above, including thex-return-urlheader for Paytrail. Paytrail returns the customer to/gift-cards/payment/success?extensionId={id}. -
Poll.
GET /api/v1/gift-cards/extension/{id}returnsstatus,paymentStatus,voucherCodeand, once applied,newExpiresAt.
| Extension status | Meaning |
|---|---|
pending | Waiting for payment |
paid | Payment received |
applied | The new expiry date is on the card |
payment_failed | Payment failed; failureReason is set |
Redeeming a gift card on a booking
A gift card code is a voucher code. Apply it to a booking hold with:
POST /api/v1/bookings/{bookingId}/apply-voucher
{ "voucherCode": "GIFT-ABCD-1234" }
The applied amount is the smaller of the card balance and the booking total. Any remaining balance stays on the card. See Bookings API for the full checkout flow.
Admin endpoints
These need a staff JWT. Reads need admin:read; changes need admin:write.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/admin/gift-cards/products | List products (isActive, siteId, page, limit) |
| POST | /api/v1/admin/gift-cards/products | Create a product |
| GET | /api/v1/admin/gift-cards/products/{id} | Get a product |
| PUT | /api/v1/admin/gift-cards/products/{id} | Update a product (any subset of fields) |
| DELETE | /api/v1/admin/gift-cards/products/{id} | Deactivate a product. Issued cards stay valid |
| GET | /api/v1/admin/gift-cards/purchases | List purchases (status, purchaserEmail, dateFrom, dateTo, postingStatus, page, limit). postingStatus=to_post is the posting queue, oldest first |
| GET | /api/v1/admin/gift-cards/purchases/{id} | Get a purchase |
| POST | /api/v1/admin/gift-cards/purchases/{id}/resend | Resend the gift card email |
| GET | /api/v1/admin/gift-cards/purchases/{id}/extensions | Extension history for a purchased card |
| GET | /api/v1/admin/gift-cards/analytics | Sales and redemption figures |
| GET | /api/v1/admin/gift-cards/purchases/postal-export | Posted cards to post as CSV (address, code, value, validity, bonus card) |
| POST | /api/v1/admin/gift-cards/purchases/mark-posted | Mark posted cards posted: { "ids": [...] } (max 200), returns { updated, skipped } |
| GET | /api/v1/admin/gift-cards/settings/postal | Postal delivery settings |
| PUT | /api/v1/admin/gift-cards/settings/postal | Update postal delivery settings (settings:write) |
| POST | /api/v1/admin/gift-cards/import/preview | Validate a CSV of existing cards |
| POST | /api/v1/admin/gift-cards/import/execute | Import the CSV |
Create a product
{
"name": "Choose your amount",
"valueType": "custom_range",
"minValue": 25,
"maxValue": 500,
"suggestedValues": [50, 100, 200],
"validityDays": 365,
"extensionEnabled": true,
"extensionPriceCents": 500,
"extensionMonths": 6,
"maxExtensions": 2,
"extensionGraceDays": 30,
"extensionGracePriceCents": 1000
}
For a fixed-value product, send "valueType": "fixed" and fixedValue instead of the range fields. Other optional fields: description, siteId (site-specific card), productIds (only valid for these products), imageUrl, displayOrder, isActive, metadata.
Import existing cards
Both import endpoints take a multipart/form-data upload with one CSV file. The header row must include code and originalValue. Optional columns: expiresAt, customerEmail, notes. Run preview first to see row-level errors, then execute to create the cards.