API reference
General rules (tokens, problem-detail errors, idempotency, time) are in Integration conventions.
You call core: the Points API
Section titled “You call core: the Points API”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} |
Transaction
Section titled “Transaction”{ "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 rules
Section titled “Field rules”| 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 |
Feed events
Section titled “Feed events”{ "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.
You host: for the app
Section titled “You host: for the app”| 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. |
You host: for your page
Section titled “You host: for your page”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 |
You host: for core
Section titled “You host: for core”| Method and path | Notes |
|---|---|
POST /auth/revoke |
Bearer <revoke token>, {extId, signedOutAt} → exactly 200. See Signing out. |
Credentials FINNS gives you
Section titled “Credentials FINNS gives you”| 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).

