Offers
Create, list, update, and change the status of gift card offers (the catalog products customers can buy).
Offers are the catalog products shown as "gift card products" in the
dashboard's Gift cards section. All offer endpoints require an authenticated
merchant session with the portal.gift_cards permission.
Offers are read from and written to the card network directly
Unlike POS orders and gift cards, offer data is not stored in the portal's
own database. List, create, and update all call through to the upstream card
network's offer API. The portal's local CatalogOffer table is a separate,
partial mirror used elsewhere (for example, to resolve a POS sale); it is not
what these endpoints read or write.
List offers
GET /api/bff/offersReturns all offers for the merchant.
Response
{ "ok": true, "data": { "items": [ ] } }Each item is an offer object normalised from the upstream response (see Get an offer for the shape).
Create an offer
POST /api/bff/offersRequest body
The request body is passed through to the upstream offer API largely as-is (no fixed schema is enforced beyond "valid JSON object"), with the merchant's upstream merchant ID attached automatically. In practice, the fields used elsewhere in this codebase when building an offer are:
| Field | Type | Notes |
|---|---|---|
name | string | |
description | string | |
giftCardOfferType | string | For example GIFTCARD_DIGITAL, REFUND_DIGITAL. |
usage | string | Where the offer can be redeemed (web, POS, or both). |
type | string | |
defaultPrice | number | Used for fixed pricing. |
minPrice / maxPrice | number | Used for variable (flexible) pricing. |
priceLocked | boolean | |
availableForPurchaseTime / expireForPurchaseTime | string | Sales period bounds. |
unlimited | boolean | Whether the sales period is unlimited. |
numberOfDaysCardIsValidFromPurchaseTime | number | |
denominations | number[] | Fixed denomination amounts. |
currency | string | |
giftCardDesign | array | Design image reference(s); see Upload offer media. |
Accuracy note
This field list is reconstructed from the update schema used by PUT /api/bff/offers/{id}
(see below) and from how the codebase populates an offer elsewhere, since the
create route itself accepts an arbitrary object and forwards it upstream. It
is a reasonable contract to follow, but it is not enforced by validation on
this endpoint the way pos/issue enforces its schema.
Response
{ "ok": true, "data": { } }data is the created offer, normalised the same way as Get an offer.
Errors
| Status | Cause |
|---|---|
| 401 | No session. |
| 404 | Session user has no associated merchant. |
| 403 | Session user's role lacks the portal.gift_cards permission. |
| 400 | Request body is not valid JSON. |
| varies | Upstream create failures pass through the upstream status code with a generic friendly message ("Request failed" for 4xx, "Service unavailable" for 5xx). |
Get an offer
GET /api/bff/offers/{id}Path parameters
| Param | Required |
|---|---|
id | yes |
Response
{
"ok": true,
"data": {
"id": "...",
"name": "...",
"status": "...",
"currency": "...",
"defaultPrice": 0,
"min": 0,
"max": 0,
"denominations": [],
"priceLocked": false,
"availableForPurchaseTime": null,
"expireForPurchaseTime": null,
"numberOfDaysCardIsValidFromPurchaseTime": 365,
"description": null,
"raw": { }
}
}The normaliser tolerates several upstream field-naming variants (for
example, accepting defaultPrice, default_price, or faceValue for the
default price). raw carries the unmodified upstream response alongside the
normalised fields.
Errors
| Status | Cause |
|---|---|
| 401 | No session. |
| 404 | Session user has no associated merchant, or the offer does not belong to this merchant (tenant check). |
| 403 | Session user's role lacks the portal.gift_cards permission. |
| 400 | Missing id path parameter. |
Update an offer
PUT /api/bff/offers/{id}Fetches the existing offer first, merges the request body over it (preserving any upstream fields not included in the request), and writes the merged result back upstream. This means a partial update only needs to include the fields being changed.
Request body
All fields optional; unrecognised fields are rejected (.strict() schema).
| Field | Type |
|---|---|
name | string (min length 1) |
description | string or null |
giftCardOfferStatus | string |
usage | string |
type | string |
defaultPrice / minPrice / maxPrice | number |
priceLocked | boolean |
availableForPurchaseTime / expireForPurchaseTime | string |
numberOfDaysCardIsValidFromPurchaseTime | number |
unlimited | boolean |
urlGiftcardResource | string |
giftCardDesign | array of objects |
denominations | number[] |
currency | string |
id / giftCardOfferId / merchantId | number / number / string |
Response
{ "ok": true, "data": { } }Same normalised offer shape as Get an offer.
Errors
| Status | Cause |
|---|---|
| 401 | No session. |
| 404 | Session user has no associated merchant, the offer ID is missing, or the offer does not belong to this merchant (tenant check). |
| 403 | Session user's role lacks the portal.gift_cards permission. |
| 400 | Missing id path parameter, or the body contains a field not in the schema above. |
Change an offer's status
PATCH /api/bff/offers/{id}/statusA narrower alternative to a full update, for changing only the offer's status.
Request body
| Field | Type | Required |
|---|---|---|
status | string, one of AVAILABLE, HIDDEN, AWAITING_APPROVAL, CANCELED, EXPIRED, ARCHIVED | yes |
Dashboard shows a narrower set
The dashboard's gift card list currently shows offers as available,
awaiting approval, or canceled (see the Merchant Guides Gift cards page).
The upstream status enum has more values than the dashboard currently
surfaces; HIDDEN, EXPIRED, and ARCHIVED are valid values here even
though they aren't part of the documented dashboard UI flow.
Response
{ "ok": true, "data": { } }Errors
Same as Update an offer.
Upload offer media
POST /api/bff/offers/mediaUploads a gift card design image to internal object storage and returns a
public URL to reference from an offer's giftCardDesign.
Request
multipart/form-data with a single field:
| Field | Type | Required |
|---|---|---|
file | file | yes |
Response
{
"ok": true,
"data": {
"urlResource": "https://app.globagift.io/api/public/gift-card-image?key=...",
"urlImage": "https://app.globagift.io/api/public/gift-card-image?key=...",
"previewUrl": "https://.../api/public/gift-card-image?key=...",
"key": "public/gift-cards/designs/<uuid>-<filename>",
"fileName": "<uuid>-<filename>"
}
}urlResource and urlImage use the canonical portal URL
(MERCHANT_PORTAL_URL, defaulting to https://app.globagift.io).
previewUrl uses the requesting host instead, useful when previewing from a
different environment.
Errors
| Status | Cause |
|---|---|
| 401 | No session. |
| 404 | Session user has no associated merchant. |
| 403 | Session user's role lacks the portal.gift_cards permission. |
| 400 | No file provided in the form data. |
| 500 | Upload to storage failed. |