Skip to content

API reference

General rules (tokens, problem-detail errors, idempotency, time) are in Integration conventions.

Base: https://test-api.finnsbali.com/api/v1/points · Authorization: Bearer <Points API token> · responses are Cache-Control: no-store.

Method and path Body Answer
POST /widget-launches/redeem {code} 200 {extId, issuedAt, notAfter} · 404 for any bad code. Never retry.
GET /customers/{extId} 200 {extId, balance, seq} (0, 0 before any posting) · 404
POST /credits {extId, points, reason?, reference?} + Idempotency-Key 201 transaction
POST /debits same 201 transaction · 409 insufficient_points
POST /transactions/{transactionId}/refund {reference?} (optional) + Idempotency-Key 201 transaction · 409 already_refunded, not_refundable, insufficient_points · 404
GET /events?after=&limit= 200 {events: [{eventId, type, extId, at, transaction?}], next}
GET /reconciliation/{YYYY-MM-DD} 200 {businessDate, timeZone, credits, debits, refunds, transactionsSha256}
{
"transactionId": "0192f3a4-…",
"kind": "credit",
"extId": "0b6c3f6e-…",
"points": 10,
"balance": 130,
"seq": 14,
"reason": "raffle_entry",
"reference": "draw-2026-10",
"refundOf": null,
"recordedAt": "2026-10-13T02:30:00Z"
}

points is signed from the member’s side (-10 for a debit). balance and seq are the member’s after this posting.

Field Rule
extId UUID; responses use lowercase
points JSON integer, 1 to 1,000,000,000; fractions and strings are invalid
reason Your code, [A-Za-z0-9_.:-]{1,32}
reference Your id, 1–128 printable ASCII, no spaces
Idempotency-Key 1–128 printable ASCII, no spaces; required on the three POSTs that post (missing is 400 malformed); never expires
{
"events": [
{ "eventId": "…", "type": "customer.created", "extId": "0b6c…", "at": "2026-10-13T01:00:00Z" },
{ "eventId": "…", "type": "points.posted", "extId": "0b6c…", "at": "2026-10-13T02:30:00Z",
"transaction": { "transactionId": "…", "kind": "credit", "extId": "0b6c…", "points": 10,
"balance": 130, "seq": 14, "reason": "check_in", "reference": "…", "recordedAt": "…" } }
],
"next": "…"
}

transaction (on points.posted only) has the same shape as above. limit is 1–500 (default 100). Pass next back as after.

Method and path Caller Notes
POST /api/v1/launch FINNS app (CORS) {code?, slots, context?} → {token, expiresIn, slots}. See Launching widgets.
GET /w/{slot} WebView / iframe Your widget page, with its CSP and frame-ancestors.

Your own design. The reference implementation uses:

Method and path Notes
GET /api/v1/points Bearer <widget token> → {balance}
POST /api/v1/points/adjust Bearer <widget token>, Idempotency-Key: <UUID>, {direction} → {balance} · 409 insufficient_points · 401 · 429 · 502
Method and path Notes
POST /auth/revoke Bearer <revoke token>, {extId, signedOutAt} → exactly 200. See Signing out.
Secret Direction
Points API token you → core
Revoke token core → you
App origins for CORS on /api/v1/launch and frame-ancestors on your page

Internal and confidential. Share partner sections only as partner builds (pnpm build:partner).