Skip to main content

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​

MethodPathPurpose
GET/api/v1/gift-cards/productsList active gift card products
GET/api/v1/gift-cards/products/{id}Get one product
POST/api/v1/gift-cards/purchaseStart a purchase
POST/api/v1/gift-cards/purchase/{id}/payment/stripeCreate a Stripe payment for a purchase
POST/api/v1/gift-cards/purchase/{id}/payment/paytrailCreate 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-eligibilityCheck whether a card can be extended
POST/api/v1/gift-cards/{code}/extensionStart a paid extension
POST/api/v1/gift-cards/extension/{id}/payment/stripeCreate a Stripe payment for an extension
POST/api/v1/gift-cards/extension/{id}/payment/paytrailCreate 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​

  1. Start the purchase with POST /api/v1/gift-cards/purchase.
  2. Pay with Stripe or Paytrail (see below).
  3. Poll GET /api/v1/gift-cards/purchase/{id} until status is paid or delivered. The response then includes voucherCode and voucherExpiresAt.

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"
}'
FieldRequiredDescription
giftCardProductIdYesGift card product ID
amountFor custom_rangeAmount in euros. Ignored for fixed-value products
purchaserEmailYesBuyer's email
purchaserNameNoBuyer's name
purchaserPhoneNoBuyer's phone
recipientEmailNoSend the card to someone else
recipientNameNoRecipient's name
personalMessageNoMessage shown in the gift email
deliveryDateNoISO date to send the gift email later
deliveryMethodNoemail (default) or post
postalAddressFor post{ name, streetAddress, postalCode, city }, Finland, 5-digit postcode
postBonusCardNoWith 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"
}
StatusMeaning
pendingWaiting for payment
payment_failedPayment failed; failureReason is set
paidPaid; the voucher code exists
deliveredThe gift card email has been sent
cancelledCancelled

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.

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.

  1. 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 eligible is false, reason is one of extension_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_reached or past_grace_window.

    A card that has already expired can still be extended during the product's grace window (extensionGraceDays). Then isGracePeriod is true and the grace price applies.

  2. Start the extension. POST /api/v1/gift-cards/{code}/extension (no body) returns extensionId, priceCents, currency, months, isGracePeriod, status: "pending" and paymentOptions.

  3. Pay. POST /api/v1/gift-cards/extension/{id}/payment/stripe or POST /api/v1/gift-cards/extension/{id}/payment/paytrail. These work exactly like the purchase payment endpoints above, including the x-return-url header for Paytrail. Paytrail returns the customer to /gift-cards/payment/success?extensionId={id}.

  4. Poll. GET /api/v1/gift-cards/extension/{id} returns status, paymentStatus, voucherCode and, once applied, newExpiresAt.

Extension statusMeaning
pendingWaiting for payment
paidPayment received
appliedThe new expiry date is on the card
payment_failedPayment 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.

MethodPathPurpose
GET/api/v1/admin/gift-cards/productsList products (isActive, siteId, page, limit)
POST/api/v1/admin/gift-cards/productsCreate 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/purchasesList 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}/resendResend the gift card email
GET/api/v1/admin/gift-cards/purchases/{id}/extensionsExtension history for a purchased card
GET/api/v1/admin/gift-cards/analyticsSales and redemption figures
GET/api/v1/admin/gift-cards/purchases/postal-exportPosted cards to post as CSV (address, code, value, validity, bonus card)
POST/api/v1/admin/gift-cards/purchases/mark-postedMark posted cards posted: { "ids": [...] } (max 200), returns { updated, skipped }
GET/api/v1/admin/gift-cards/settings/postalPostal delivery settings
PUT/api/v1/admin/gift-cards/settings/postalUpdate postal delivery settings (settings:write)
POST/api/v1/admin/gift-cards/import/previewValidate a CSV of existing cards
POST/api/v1/admin/gift-cards/import/executeImport 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.