List loyalty ledger entries

Returns a paginated history of loyalty token transactions for the authenticated user.

Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…

Why this endpoint exists

The balance is a number; this is the story behind it. Every award, spend and reversal, newest first — what a customer needs to answer "where did my points go?" without contacting support.

It is also the audit trail. Each entry names the flow that created it, so a discrepancy can be traced to a specific voucher, checkout or partner sync.

Request

curl "https://api.acc.funtrips.io/v2/me/loyalty/ledger/entries?limit=20" \
  -H "x-campaign-id: $CAMPAIGN_ID" \
  -H "Authorization: Bearer $END_USER_TOKEN"
ParameterTypeNotes
limitinteger1–100, default 20
next_tokenstringCursor from meta.pagination.next_token

Response

{
  "data": [
    {
      "entry_id": "voucher:WELCOME2026",
      "campaign_id": "3c1e5a90-7b2d-4f61-9a83-1d4e7f2b8c05",
      "external_user_id": "customer-4815162342",
      "type": "award",
      "tokens": 500,
      "status": "BOOKED",
      "balance_after": null,
      "reason": "Welcome voucher",
      "reason_code": "VOUCHER_REDEEMED",
      "source": "vouchers",
      "meta": null,
      "report_date":    "2026-09-01T10:00:00Z",
      "creation_date":  "2026-09-01T10:00:03Z",
      "processed_date": "2026-09-01T10:00:04Z"
    },
    {
      "entry_id": "checkout-discount:7c1b3f9a-2d4e-4a6f-9b1a-3c5e7d2f8a0b",
      "type": "spend",
      "tokens": 1100,
      "status": "BOOKED",
      "…": "…"
    }
  ],
  "meta": {
    "correlation_id": "…",
    "item_count": 20,
    "pagination": { "limit": 20, "next_token": "eyJwayI6…" }
  }
}

Reading an entry

FieldMeaning
entry_idDeterministic id, prefixed by the originating flow. Opaque string, not a UUID
typeaward (tokens in) or spend (tokens out)
tokensAlways positive. type carries the direction
statusPENDING, BOOKED or FAILED
balance_afterReserved — always null today
reason / reason_codeHuman-readable and machine-readable explanation
sourceThe subsystem that wrote it
report_dateWhen the movement is considered to have happened. May predate creation_date
creation_dateWhen it was written to the ledger
processed_dateWhen it reached a final status. null while PENDING

tokens is unsigned — read type

A spend of 1100 reduces the balance by 1100. There are no negative token values.

const signed = entry.type === 'spend' ? -entry.tokens : entry.tokens;

Handle these two types. A refund of previously spent tokens arrives as an award — see
Refunds look like awards below for how to label it distinctly.

Only BOOKED entries have moved the balance

PENDING has not been applied yet. FAILED never will be. If you sum entries to explain a balance, filter to BOOKED — otherwise your total will not match the balance endpoint, and the customer will believe one of the two screens is lying.

balance_after is always null

Do not build a running-balance column from balance_after.

The field is reserved for future use and is not populated.

To show a running balance, walk BOOKED entries backwards from the current balance — and read
the ordering note below before you do.

report_date can predate creation_date

An integrator may supply the date a movement actually happened, which can be earlier than when the platform recorded it — a backfilled in-store purchase, for instance.

Display report_date ("earned on 1 September"), because that is what the customer recognises. Be aware that a list sorted newest-first by ledger order is not necessarily sorted by report_date, so a naive running-balance walk can produce a sequence that looks wrong to a customer reading down the page.

entry_id tells you where it came from

The prefix names the flow that produced the entry:

entry_idOrigin
voucher:<code>A voucher redemption awarded tokens
checkout-discount:<checkout_id>Loyalty spent on a checkout
loyalty_sync:<…>A partner balance sync corrected a drift
<original_entry_id>:reversalA refund of a checkout discount
A bare ksuid, e.g. 2gK8xQ1mZp4vY7nB3tL6wZ9hJWritten by the retailer through POST /admin/loyalty/ledgers/{id}/entries

Retries of the same upstream event collapse onto the same id, which is what makes the ledger idempotent. That also makes entry_id the right thing to quote in a support conversation — it points at a specific cause.

Refunds look like awards

When a checkout is reversed, the tokens burned on it are credited back as an award entry — id
<original_entry_id>:reversal, reason_code: CHECKOUT_DISCOUNT_REVERSAL,
source: checkout_discount_reversal. To label refunds distinctly in a history view, match on the
reason code or the id suffix rather than on type.

Errors

StatusMeaning
401Token missing, expired or invalid
500Unexpected fault. Retryable

A customer with no history gets 200 with an empty array. Unlike the balance endpoint, there is no 404 for a customer who has never transacted.

Notes

  • external_user_id is PII-adjacent. Do not log entries wholesale; log entry_id and the correlation id.
  • Formatting needs the unit. As with the balance, tokens has no unit — read token_unit from campaign config.
Query Params
integer
1 to 100
Defaults to 20

Number of items to return per page.

string

Opaque pagination cursor returned by a previous response.

Headers
uuid
required

Campaign scope for this request. Filters content to the specified campaign.

string
enum
Defaults to application/json

Generated from available response content types

Allowed:
Responses

Language
Credentials
Bearer
JWT
URL
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json
application/problem+json