Developer docs

Getting started

Two API surfaces: the tenant API (issue and redeem cards under your brand) and the retailer POS API (your till software activates consigned cards directly). Both are Bearer-key authenticated, JSON in and out.

Authentication

Tenant API keys look like gft_… and carry one of two scopes:

  • redeem — validate and redeem codes; the scope your storefront or app uses.
  • manage — batch creation, exports, rollbacks, resellers, reports. Never ship this key client-side.
Authorization: Bearer gft_xxxxxxxxxxxxxxxxxxxxxxxx

Retailer POS keys look like gfr_… and scope to a single shop:

Authorization: Bearer gfr_xxxxxxxxxxxxxxxxxxxxxxxx

Validate a code

Read-only preview — doesn't consume the card. redeem scope.

POST /api/v1/validations
{
  "code": "XXXXXXXXXXXXXXXX",
  "externalUserRef": "user_123"   // or send X-End-User-Ref header
}

200 OK
{
  "valid": true,
  "serial": "AB12-CD34",
  "productRef": "gift-6m",
  "faceValue": 1000,
  "status": "active"
}

Unknown, invalid, and expired codes all return the identical { valid: false, serial: null, productRef: null, faceValue: null, status: null } shape — the API never leaks whether a code exists.

Redeem a code

One call flips the card and records the redemption atomically. Idempotent on idempotencyKey — safe to retry a timed-out request.

POST /api/v1/redemptions
{
  "code": "XXXXXXXXXXXXXXXX",
  "idempotencyKey": "order_9f3a...",
  "externalUserRef": "user_123",
  "confirm": true    // optional; omit or true = one-phase (default)
}

201 Created
{
  "id": "rdm_...",
  "serial": "AB12-CD34",
  "productRef": "gift-6m",
  "faceValue": 1000,
  "status": "completed",
  "reservedUntil": null,
  "redeemedAt": "2026-08-07T12:00:00.000Z"
}

Two-phase mode — pass "confirm": false to reserve the card (10-minute TTL) before you've actually granted value, then confirm once you have:

POST /api/v1/redemptions/:id/confirm

200 OK
{
  "id": "rdm_...",
  "serial": "AB12-CD34",
  "productRef": "gift-6m",
  "faceValue": 1000,
  "status": "completed",
  "reservedUntil": null,
  "redeemedAt": "2026-08-07T12:00:00.000Z"
}

A reservation that's never confirmed expires on its own and the card returns to active. Replaying the same idempotencyKey with the same code always returns the original result; reusing it with a different code is a 409.

Product catalogue

GET /api/v1/products

200 OK
{
  "products": [
    { "ref": "gift-6m", "label": "6-month card", "durationMonths": 6, "faceValue": 1000, "currency": "USD" }
  ]
}

Status

Unauthenticated, cacheable — poll this to degrade gracefully instead of hard-failing your redeem page.

GET /api/v1/status

200 OK
{ "status": "ok", "time": "2026-08-07T12:00:00.000Z" }

Retailer POS API

For a shop's own till software — activates a card consigned to that reseller. Same idempotency contract as redemptions.

POST /api/ra/v1/activations
{
  "serial": "AB12-CD34",
  "cashierRef": "till-04",
  "idempotencyKey": "sale_9f3a...",
  "customerPhone": "+94771234567",   // optional, for receipt
  "customerEmail": "buyer@example.com" // optional, for receipt
}

201 Created
{
  "ok": true,
  "serial": "AB12-CD34",
  "productRef": "gift-6m",
  "faceValue": 1000,
  "currency": "USD",
  "activatedAt": "2026-08-07T12:00:00.000Z"
}

Reconcile what you recorded against what we recorded — a simple cursor over recent activations for this key's shop:

GET /api/ra/v1/activations?since=2026-08-01T00:00:00Z&limit=100

200 OK
{
  "activations": [
    {
      "idempotencyKey": "sale_9f3a...",
      "cashierRef": "till-04",
      "serial": "AB12-CD34",
      "productRef": "gift-6m",
      "faceValue": 1000,
      "currency": "USD",
      "activatedAt": "2026-08-07T12:00:00.000Z",
      "recordedAt": "2026-08-07T12:00:00.100Z"
    }
  ],
  "nextSince": "2026-08-07T12:00:00.100Z"
}

Receipts

Pass customerPhone and/or customerEmail on activation and the buyer gets an SMS/email receipt automatically — no separate call needed. This never blocks or fails the sale itself.

Errors

Standard HTTP status codes. On the redeem-scope endpoints, unknown and invalid codes are deliberately indistinguishable — same shape, same timing — so a client integration can never be used to probe which codes exist.

Need a key, or more detail on any of this? Email gifts@developeroffice.com.