Overview

This page documents the v1 endpoints you’ll call from your client. You’ll be provisioned an API key out-of-band by Uptop — drop it into API settings (top-right) and you can exercise every endpoint from this page via the Try it panels.

Administrative actions (creating rewards, configuring sponsors, opening auctions, manual point adjustments, browsing all redemptions) live on the Uptop dashboard, not in your client integration. The Dashboard tab in this app simulates that surface so you can see what your administrators will be doing on the other side.

Selected environment · Dev
https://insomniac-api-dev.uptop.xyz

Try it on this page hits this host. Switch environments in the top bar.

Production
https://insomniac-api.uptop.xyz

Use this host when you ship your integration.

Authentication

Every request must include your provisioned API key in the Api-Key header. Keys are issued by Uptop — treat them like any other production secret and keep them on the server side. The server also accepts Authorization: Bearer <key> as a convenience.

every request
Api-Key: <your-uptop-api-key>
Content-Type: application/json
typescript / fetch
const response = await fetch(
  "https://insomniac-api-dev.uptop.xyz/v1/auctions",
  {
    method: "GET",
    headers: {
      "Api-Key": process.env.UPTOP_API_KEY!,
    },
  },
);

Errors

Every non-2xx response is wrapped in a stable envelope so you can branch on error.code without parsing strings.

Envelope

FieldTypeDescription
error.codeV1ErrorCodeStable machine-readable code.
error.messagestring
error.statusnumber

Example

error response
{
  "error": {
    "code": "INSUFFICIENT_POINTS",
    "message": "User does not have enough points for this redemption.",
    "status": 400
  }
}

Error codes

CodeHTTPWhen
UNAUTHORIZED401Missing, malformed, or revoked `Api-Key` header.
NOT_FOUND404Unknown `userId`, `auctionId`, `redemptionId`, or `sponsorId`.
VALIDATION_ERROR400DTO validation failed, `rewardId`/`auctionId` mismatch on redemption, or missing `variantSku` for a merch redemption.
AUCTION_CLOSED400Auction is paused, cancelled, or past its end date.
INSUFFICIENT_POINTS400User balance is below the auction's `currentPrice`.
INSUFFICIENT_FUNDS402Wallet cash, not points: the withdrawable balance is short. Withdrawals only.
WITHDRAWAL_LIMIT_EXCEEDED400The withdrawal would go over the daily outbound limit.
FORBIDDEN403The operation is refused for this user or program — e.g. a withdrawal declined by a risk check, or withdrawals not enabled.
INTERNAL_ERROR500Anything unexpected — check server logs.

Typical client flow

A common end-to-end loop for a client app:

  1. On sign-in, call POST /users:upsert with your externalUserId + email and cache the returned id.
  2. Render the storefront by fetching GET /auctions?status=open and the corresponding GET /rewards/{rewardId}. Display currentPrice as the live points cost.
  3. Show the user’s balance with GET /users/{userId}/points/balance and (optionally) their history via GET /users/{userId}/points/events.
  4. Claim a reward by calling POST /users/{userId}/redemptions. Catch AUCTION_CLOSED and INSUFFICIENT_POINTS explicitly — they’re the two recoverable error states.
  5. Show past redemptions via GET /users/{userId}/redemptions.

Users

2 endpoints

Upsert a user keyed by `email` (idempotent — repeat on every sign-in to keep the record in sync), then retrieve the full profile including live wallet balance by `id`.

Upsert user

Look up an Uptop user by `email` and create it if it doesn't exist. A wallet is provisioned automatically on creation. Cache the returned `id` — it's the only key the rest of the API takes.

POST/v1/users:upsert

Request body

FieldTypeRequiredDescription
emailstring (email)requiredPrimary identifier. Upsert is idempotent on this. Also required for virtual card issuance.
phoneNumberstring (E.164)User's phone number in E.164 format, e.g. `+12025551234`. **Required for virtual card issuance.** Kept in sync — re-upsert with a new value to update it.
externalUserIdstringYour partner-side identifier for this user. Optional. When supplied it's linked to the matched/created record so subsequent partner lookups by it work too.
firstNamestringUser's first name. **Required for virtual card issuance** — used as the cardholder billing name on the virtual card. Kept in sync on every upsert.
lastNamestringUser's last name. **Required for virtual card issuance** — used as the cardholder billing name on the virtual card. Kept in sync on every upsert.
example body
{
  "email": "fan@example.com",
  "phoneNumber": "+12025551234",
  "externalUserId": "partner-user-001",
  "firstName": "Taylor",
  "lastName": "Fan"
}

Response — 200

ReturnsUser

Click the type for the full field-by-field shape.

example response (200)
{
  "id": "65f3a91d6f3a4b0012ab34cd",
  "email": "fan@example.com",
  "phoneNumber": "+12025551234",
  "externalUserId": "partner-user-001",
  "firstName": "Taylor",
  "lastName": "Fan",
  "createdAt": "2026-05-22T15:00:00.000Z"
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • VALIDATION_ERROR400`email` missing or malformed, or `phoneNumber` supplied but not valid E.164.

Notes

  • **All four fields are required to issue a virtual card: `email`, `phoneNumber`, `firstName`, and `lastName`.** These are used as the cardholder identity on the card record. A user missing any of them cannot be issued a virtual card — pass all four on first upsert.
  • **No formal KYC is performed.** These fields are used for card issuance purposes only. Uptop does not run identity verification or background checks on this information. Wallets are capped at $1,000, which falls below the threshold that would require KYC.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Get user

Fetch the user's profile from Uptop plus their live wallet balance. Use this to display the current balance before prompting a load or redemption.

GET/v1/users/{userId}

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Response — 200

ReturnsUserDetail

Click the type for the full field-by-field shape.

example response (200)
{
  "id": "65f3a91d6f3a4b0012ab34cd",
  "email": "fan@example.com",
  "phoneNumber": "+12025551234",
  "externalUserId": "partner-user-001",
  "createdAt": "2026-05-22T15:00:00.000Z",
  "balance": {
    "amount": 2500,
    "currency": "usd"
  }
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404Unknown `userId`.
  • INTERNAL_ERROR502Wallet service unavailable.

Notes

  • `balance.amount` is in the **smallest currency unit** — cents for USD. Divide by 100 to display dollars.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Wallet

8 endpoints

Virtual wallet attached to every user. Use the virtual-card endpoint to retrieve display-safe card details for showing the card in-app. Transactions are cursor-paginated — store the `nextCursor` from each response and pass it as `cursor` on the next request to page forward through history. Funds leave the wallet through `wallet/withdrawals`, an ACH credit to a bank account the user has linked. That credit cannot be recalled once submitted, which is why the endpoint requires an `idempotencyKey`. Read `wallet/withdrawable-balance` first: it reports how much of the balance a withdrawal will actually accept, which is lower whenever funds are still clearing or a card hold is open.

Get virtual card

Returns display-safe virtual card details — network, last four digits, expiry, cardholder name, and state. Does not expose the full card number or CVV.

GET/v1/users/{userId}/wallet/virtual-card

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Response — 200

Click the type for the full field-by-field shape.

example response (200)
{
  "id": "47d688b2-c0d6-46a2-81e5-2baa979a5aa2",
  "type": "VIRTUAL",
  "cardNetwork": "mastercard",
  "lastFour": "8414",
  "expMonth": "08",
  "expYear": "2029",
  "cardHolderName": "Tony Lee",
  "state": "OPEN"
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404userId does not exist, no virtual card has been provisioned for this user, or the merchant has no active virtual card program.
  • UNPROCESSABLE_ENTITY422The user is missing one or more fields required for virtual-card operations: `email`, `phoneNumber`, `firstName`, or `lastName`. Upsert the user with all four fields first.
  • INTERNAL_ERROR502Wallet service unavailable.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

List wallet transactions

Returns a cursor-paginated list of wallet transactions for a user, newest first. Omit `cursor` for the first page; pass the returned `nextCursor` on each subsequent call.

GET/v1/users/{userId}/wallet/transactions

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Query parameters

FieldTypeRequiredDescription
limitintegerMax transactions per page. Default `20`, max `50`.
cursorstringPagination cursor from a previous response's `nextCursor`. Omit for the first page.

Response — 200

FieldTypeDescription
transactionsWalletTransaction[]Ordered newest-first.
hasMoreboolean`true` when additional pages exist.
nextCursorstringPass as `cursor` to fetch the next page. Absent when `hasMore` is `false`.
example response (200)
{
  "transactions": [
    {
      "id": "txn-abc123",
      "type": "use_balance",
      "amount": -1500,
      "currency": "usd",
      "createdAt": "2026-06-01T18:30:00Z",
      "description": "Spend"
    },
    {
      "id": "txn-def456",
      "type": "add_balance",
      "amount": 10000,
      "currency": "usd",
      "createdAt": "2026-05-28T12:00:00Z",
      "description": "Card load"
    }
  ],
  "hasMore": true,
  "nextCursor": "cursor_abc123"
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404userId does not exist.
  • INTERNAL_ERROR502Wallet service unavailable.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

List payment methods

Returns the funding sources attached to the user's wallet — the cards (and bank accounts) used to load balance. Each entry is display-safe: brand and last four only, never the full number. The `preferred` method is the default used by add-balance when no `paymentMethodId` is supplied. Returns an empty array when no payment method is on file.

GET/v1/users/{userId}/wallet/payment-methods

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Response — 200

FieldTypeDescription
paymentMethodsPaymentMethod[]Attached funding sources. Empty when none are on file.
example response (200)
{
  "paymentMethods": [
    {
      "id": "pm-9f2c1a7b",
      "type": "card",
      "brand": "visa",
      "last4": "4242",
      "expMonth": 12,
      "expYear": 2027,
      "preferred": true
    }
  ]
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404userId does not exist.
  • INTERNAL_ERROR502Wallet service unavailable.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Get withdrawable balance

Returns how much of the wallet balance a withdrawal will accept right now, alongside the wallet balance itself. The two differ because promotional and other non-cash funds cannot be cashed out, recently added funds are held until they clear, an open card authorization blocks withdrawals entirely, and the per-withdrawal minimum and maximum bound the result. `withdrawableBalance` is therefore always an amount a withdrawal accepts, or `0`. Call this before rendering a withdrawal screen and bind any "maximum" control to `withdrawableBalance` rather than to `balance`. Both figures come from a single read, so they cannot disagree; this endpoint replaces `GET /users/{userId}/wallet` on that screen rather than supplementing it. Accepts either the partner `Api-Key` or a user-scoped client token.

GET/v1/users/{userId}/wallet/withdrawable-balance

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Response — 200

Click the type for the full field-by-field shape.

example response (200)
{
  "balance": 15336,
  "withdrawableBalance": 13336,
  "currency": "usd",
  "reason": "held_recent_load"
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key` or client token.
  • FORBIDDEN403Withdrawals are not configured for the program, in which case the withdrawal endpoint returns the same code and the screen should not be offered. Also returned when a client token is scoped to a different user than the one in the path.
  • NOT_FOUND404`userId` does not exist, or the user has no wallet.
  • INTERNAL_ERROR502The wallet service was unavailable or did not respond in time.

Notes

  • **`reason` is present only when `withdrawableBalance` is below `balance`.** Five values are defined today. `open_card_hold`: a pending card authorization is blocking withdrawals, and `withdrawableBalance` is `0`. `held_recent_load`: recently added funds have not finished clearing. `non_cash_funds`: part of the balance is promotional or otherwise not cashable. `maximum_withdrawal`: the balance exceeds the per-withdrawal maximum, and `withdrawableBalance` is that maximum. `below_minimum_withdrawal`: what remains is under the per-withdrawal minimum, so `withdrawableBalance` is `0`. Only one reason is reported when several apply, whichever determines the returned figure. Treat an unrecognized value as a generic explanation rather than an error, since further values may be added.
  • **Do not publish clearing dates or per-reason amounts.** The response deliberately carries neither. The holding period is a fraud control, so stating when funds unlock, or how much each constraint holds, publishes the control. Wording such as "some funds are still processing" tells the user what they need to know without exposing it.
  • **The per-withdrawal minimum and maximum are already applied.** `withdrawableBalance` never exceeds the largest amount one withdrawal accepts, and is reported as `0` rather than as an amount below the minimum. A control bound to it therefore cannot produce an amount refused for being out of bounds, and no integration needs to hardcode either limit.
  • **The figure is a snapshot, not a reservation.** A card authorization or a settling load can change it moments later, so `POST /users/{userId}/wallet/withdrawals` remains the authority. Read it again after an `INSUFFICIENT_FUNDS` refusal rather than reusing the value on screen.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Create a withdrawal

Submits an ACH credit from the user's wallet to a bank account they have linked. The wallet is debited immediately and the response status is always `pending`; the credit settles over the following business days and its outcome appears as later entries in `GET /users/{userId}/wallet/transactions` rather than on this response. `paymentMethodId` is required and must identify a bank account, so complete Plaid Link before calling this endpoint. Accepts either the partner `Api-Key`, which may act on any user, or a user-scoped client token from `POST /users/{userId}/auth/client-token`. Every request requires an `idempotencyKey`; see the notes for its lifecycle.

POST/v1/users/{userId}/wallet/withdrawals

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Request body

FieldTypeRequiredDescription
amountCentsintegerrequiredAmount to withdraw, in cents. The minimum is `100` ($1.00). Amounts above the per-withdrawal cap are refused with `VALIDATION_ERROR`.
paymentMethodIdstringrequiredDestination bank account: an `id` from `GET /users/{userId}/wallet/payment-methods` with `type: "bank_account"`, or the `paymentMethodId` returned when the account was linked. Cards are rejected. The field has no default, so the destination is always explicit.
idempotencyKeystring (UUID)requiredIdentifies one withdrawal intent and must stay constant across every retry of that intent, so that a replay returns the original transaction rather than submitting a second ACH credit. Generate a new key whenever the amount or destination changes; reusing a key with a different amount returns 409.
example body
{
  "amountCents": 5000,
  "paymentMethodId": "pm-3c8b1e94",
  "idempotencyKey": "6f8b0d4a-2e51-4b7c-9a13-8c5d2f0e7b64"
}

Response — 201

ReturnsWithdrawal

Click the type for the full field-by-field shape.

example response (201)
{
  "transactionId": "txn-7b21c4de",
  "status": "pending",
  "amountCents": 5000,
  "paymentMethodId": "pm-3c8b1e94",
  "balance": 10336,
  "currency": "usd",
  "createdAt": "2026-08-24T15:04:00.000Z"
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key` or client token.
  • VALIDATION_ERROR400`amountCents` is below `100` or over the per-withdrawal cap, `idempotencyKey` is missing or not a UUID, or `paymentMethodId` is a card rather than a bank account.
  • VALIDATION_ERROR409The `idempotencyKey` was already used for a withdrawal of a different amount, which identifies a stale key rather than a replay. Create a new intent with a new key; do not retry this request.
  • INSUFFICIENT_FUNDS402The withdrawable balance is below `amountCents`, because the wallet holds less than that amount, a recent load has not cleared, or a card hold is open. Retriable once the funds clear.
  • WITHDRAWAL_LIMIT_EXCEEDED400The request exceeds the daily outbound limit. Retriable with a smaller amount or on a later day.
  • FORBIDDEN403Declined by a risk check, or withdrawals are not yet configured for the program. Not retriable; contact your account contact.
  • NOT_FOUND404`userId` does not exist, or `paymentMethodId` is not a payment method on this user's wallet.
  • INTERNAL_ERROR502The wallet service was unavailable or did not respond in time. The withdrawal may or may not have been created; retry with the same `idempotencyKey`.

Notes

  • **Reuse the `idempotencyKey` on every retry.** A request that times out and is retried under a new key submits a second ACH credit. Generate one UUID per withdrawal intent, meaning the amount and destination the user confirmed, persist it before the first request, and send that same key, amount, and destination on every retry. Discard the key after a 2xx, and generate a new one when the user changes the amount or destination.
  • **A timeout or a 502 is an unknown outcome, not a failure.** The withdrawal may already exist. Retry with the same key, since a replay returns the original transaction rather than submitting a second ACH credit. Do not resolve the ambiguity by creating a new withdrawal, and note that the transaction list cannot identify one: transaction records carry no idempotency key, so two withdrawals of the same amount to the same destination are indistinguishable.
  • **Check the withdrawable balance before submitting.** The wallet balance is an upper bound: recently added funds are held until they clear, an open card hold blocks withdrawals entirely, and the per-withdrawal limits bound what one request may take. `GET /users/{userId}/wallet/withdrawable-balance` reports the amount this endpoint will accept, and any maximum offered to the user should come from there. That figure is a snapshot rather than a reservation, so still handle `INSUFFICIENT_FUNDS` and read it again before retrying.
  • **Withdrawing recently added funds can deduct points.** Programs may award points when funds are added to the wallet; when recently added funds leave as cash rather than being spent, the points awarded for the withdrawn amount are deducted again. Withdrawing part of a recent load deducts the bonus on that part only, and one load is never deducted twice across withdrawals. The deduction posts as a negative-`delta` entry of type `other` in `GET /users/{userId}/points/events`, and the points balance can go negative when those points were already redeemed. In-venue spending never triggers this. The lookback window, like the funds-availability holds, is a fraud control and is not disclosed.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Create & push-provision virtual card

Issues the user's virtual card (if they don't have one yet) and pushes it into Apple Pay or Google Pay in a single call. The virtual card is a singleton per user, so this endpoint is idempotent — it reuses the existing card when one is present and only creates a card the first time. The device generates cryptographic material (nonce, nonce signature, certificate) which this endpoint forwards to the card network. An opaque provisioning payload is returned for the device to complete the "Add to Wallet" flow.

POST/v1/users/{userId}/wallet/virtual-card/provision

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Request body

FieldTypeRequiredDescription
digitalWallet"APPLE_PAY" | "GOOGLE_PAY"requiredTarget digital wallet.
noncestringApple Pay only. Base64-encoded cryptographic nonce generated by the device.
nonceSignaturestringApple Pay only. Base64-encoded signature over the nonce generated by the device.
certificatestringApple Pay only. Apple's public leaf certificate, Base64-encoded PEM (headers omitted).
example body
{
  "digitalWallet": "APPLE_PAY",
  "nonce": "base64nonce==",
  "nonceSignature": "base64sig==",
  "certificate": "base64cert=="
}

Response — 200

FieldTypeDescription
(provisioning payload)objectOpaque cryptographic data. Pass this directly to the device's wallet SDK to complete provisioning.
example response (200)
{}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404userId does not exist, or the merchant has no active virtual card program (no card can be issued).
  • VALIDATION_ERROR400digitalWallet is missing, or nonce/nonceSignature/certificate are absent for APPLE_PAY.
  • UNPROCESSABLE_ENTITY422The user is missing one or more fields required for virtual-card operations: `email`, `phoneNumber`, `firstName`, or `lastName`. Upsert the user with all four fields first.
  • INTERNAL_ERROR502Card provisioning service unavailable.

Notes

  • This is the only endpoint that issues a virtual card — it creates one on the first call and reuses it on every call after, then push-provisions it. Because the card is a singleton, the endpoint is idempotent and safe to retry.
  • **Cardholder-identity prerequisites — all four must be on file:** `email`, `phoneNumber`, `firstName`, `lastName`. They become the cardholder identity on the card record and are required by every virtual-card endpoint (issue, push-provision, and fetch). Upsert the user with all four before provisioning, or you'll get a 422.
  • **No formal KYC is performed.** These fields are used for card issuance only — Uptop does not run identity verification on them. The $1,000 wallet cap keeps the program below the threshold that would require KYC.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Sponsors

2 endpoints

Sponsors are the brands whose spend triggers points (linked-card, receipt scan, or both). Configuration happens on the Uptop dashboard — you only need to read them to render sponsor cards in-app.

List sponsors

All sponsors, in registration order.

GET/v1/sponsors

Response — 200

ReturnsSponsor[]

Click the type for the full field-by-field shape.

example response (200)
[
  {
    "id": "65f3a91d6f3a4b0012ab40aa",
    "name": "Acme Coffee",
    "description": "Local coffee partner",
    "logoUrl": "https://example.com/acme.png",
    "primaryColor": "#1D428A",
    "secondaryColor": "#FFFFFF",
    "matchingType": "receipt",
    "pointsMultiplier": 2,
    "frontendMultiplier": 2,
    "priority": 10,
    "unavailable": false,
    "createdAt": "2026-05-22T15:00:00.000Z"
  }
]

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Get sponsor

Full record for a single sponsor.

GET/v1/sponsors/{sponsorId}

Path parameters

FieldTypeRequiredDescription
sponsorIdstringrequiredSponsor id from the list endpoint.

Response — 200

ReturnsSponsor

Click the type for the full field-by-field shape.

example response (200)
{
  "id": "65f3a91d6f3a4b0012ab40aa",
  "name": "Acme Coffee",
  "description": "Local coffee partner",
  "logoUrl": "https://example.com/acme.png",
  "primaryColor": "#1D428A",
  "secondaryColor": "#FFFFFF",
  "matchingType": "receipt",
  "pointsMultiplier": 2,
  "frontendMultiplier": 2,
  "priority": 10,
  "unavailable": false,
  "createdAt": "2026-05-22T15:00:00.000Z"
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404Unknown `sponsorId`.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Rewards

2 endpoints

Rewards are the things fans can redeem (tickets, experiences, merch, collectibles). They carry no `pointsCost` — price comes from the auction. Created/edited from the Uptop dashboard; clients only need to list/read.

List rewards

All rewards.

GET/v1/rewards

Response — 200

ReturnsReward[]

Click the type for the full field-by-field shape.

example response (200)
[
  {
    "id": "65f3a91d6f3a4b0012ab50bb",
    "name": "VIP locker room tour",
    "description": "Walk-through after the game.",
    "iconUrl": "https://example.com/icon.png",
    "buttonColor": "#1D428A",
    "type": "experience",
    "formFields": [
      {
        "name": "first_name",
        "type": "text",
        "required": true
      },
      {
        "name": "last_name",
        "type": "text",
        "required": true
      },
      {
        "name": "phone_number",
        "type": "text",
        "required": true
      }
    ],
    "tags": [
      "members-only"
    ],
    "createdAt": "2026-05-22T15:00:00.000Z"
  }
]

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Get reward

Full record for a single reward, including its `formFields` (what the user has to fill in to redeem) and merch variants if applicable.

GET/v1/rewards/{rewardId}

Path parameters

FieldTypeRequiredDescription
rewardIdstringrequiredReward id from the list endpoint.

Response — 200

ReturnsReward

Click the type for the full field-by-field shape.

example response (200)
{
  "id": "65f3a91d6f3a4b0012ab50bb",
  "name": "Home jersey",
  "description": "Replica home jersey",
  "iconUrl": "https://example.com/j.png",
  "buttonColor": "#FFFFFF",
  "type": "merch",
  "formFields": [
    {
      "name": "first_name",
      "type": "text",
      "required": true
    },
    {
      "name": "last_name",
      "type": "text",
      "required": true
    },
    {
      "name": "address_line_1",
      "type": "text",
      "required": true
    },
    {
      "name": "city",
      "type": "text",
      "required": true
    },
    {
      "name": "state",
      "type": "text",
      "required": true
    },
    {
      "name": "zip",
      "type": "text",
      "required": true
    }
  ],
  "merch": {
    "variants": [
      {
        "sku": "home-jersey-M",
        "color": "blue",
        "size": "M",
        "inventory": 5
      }
    ]
  },
  "tags": [
    "fan-favorite",
    "summer-drop"
  ],
  "createdAt": "2026-05-22T15:00:00.000Z"
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • VALIDATION_ERROR400Unknown `rewardId`.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Auctions

2 endpoints

Auctions price rewards in points. `currentPrice` is calculated on read for `open`/`paused` auctions and is `null` for `ended`/`cancelled`. Create/edit/pause/resume/cancel are dashboard-driven; you'll only ever read them.

List auctions

All auctions; optionally filter by status. Pass `expand=reward` to inline each auction's full `Reward` and render a storefront in one request.

GET/v1/auctions

Query parameters

FieldTypeRequiredDescription
status"open" | "paused" | "cancelled" | "ended"Filter by lifecycle state.
expand"reward"When set, every row carries the full `Reward` object under `reward` — same shape as `GET /rewards/{rewardId}` — instead of just `rewardId`. Saves the per-auction reward fetch (1 + N calls become 1).

Response — 200

ReturnsAuction[]

Click the type for the full field-by-field shape.

example response (200)
[
  {
    "id": "65f3a91d6f3a4b0012ab60cc",
    "rewardId": "65f3a91d6f3a4b0012ab50bb",
    "startPrice": 1000,
    "reservePrice": 100,
    "currentPrice": 750,
    "startDate": "2026-05-22T14:00:00.000Z",
    "endDate": "2026-05-22T16:00:00.000Z",
    "quantity": 5,
    "quantityPurchased": 1,
    "status": "open",
    "createdAt": "2026-05-22T13:50:00.000Z"
  }
]

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.

Notes

  • The example above shows the default (unexpanded) response. With `?expand=reward`, each row additionally includes a `reward` object — see the `Auction` type for the field and the `Reward` type for its shape.
  • If an auction's underlying reward no longer exists, the `reward` field is omitted for that row rather than returning an error.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Get auction

Single auction. `currentPrice` is computed at read time. Supports `expand=reward` like the list endpoint.

GET/v1/auctions/{auctionId}

Path parameters

FieldTypeRequiredDescription
auctionIdstringrequiredAuction id.

Query parameters

FieldTypeRequiredDescription
expand"reward"When set, the full `Reward` object is inlined under `reward`.

Response — 200

ReturnsAuction

Click the type for the full field-by-field shape.

example response (200)
{
  "id": "65f3a91d6f3a4b0012ab60cc",
  "rewardId": "65f3a91d6f3a4b0012ab50bb",
  "startPrice": 1000,
  "reservePrice": 100,
  "currentPrice": 750,
  "startDate": "2026-05-22T14:00:00.000Z",
  "endDate": "2026-05-22T16:00:00.000Z",
  "quantity": 5,
  "quantityPurchased": 1,
  "status": "open",
  "createdAt": "2026-05-22T13:50:00.000Z"
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404Unknown `auctionId`.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Points

2 endpoints

Per-user points balance and ledger. Manual adjustments are admin-only and handled from the dashboard.

Get user points balance

Available, pending, and lifetime-earned points. Card-spend earns start in `pendingBalance` (estimated from the authorized amount) and move to `currentBalance` (spendable) once the card transaction settles — credited at the amount that actually settled, so the final value can differ from the pending estimate (a fully reversed charge earns none).

GET/v1/users/{userId}/points/balance

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Response — 200

Click the type for the full field-by-field shape.

example response (200)
{
  "userId": "65f3a91d6f3a4b0012ab34cd",
  "currentBalance": 4250,
  "pendingBalance": 750,
  "lifetimeEarned": 5000
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404Unknown `userId`.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

List user points events

Ledger of point movements for the user, newest first. Card spend surfaces as `'spend'`; other internal types (referrals, badges, …) collapse to `'other'`.

GET/v1/users/{userId}/points/events

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Query parameters

FieldTypeRequiredDescription
type"adjustment" | "receipt" | "daily_claim" | "reward_claim" | "spend"Optional filter. Only the public event types are exposed by name.

Response — 200

Click the type for the full field-by-field shape.

example response (200)
[
  {
    "id": "65f3a91d6f3a4b0012ab70dd",
    "userId": "65f3a91d6f3a4b0012ab34cd",
    "type": "adjustment",
    "delta": 5000,
    "reason": "manual_seed",
    "createdAt": "2026-05-22T15:01:00.000Z"
  },
  {
    "id": "65f3a91d6f3a4b0012ab70ee",
    "userId": "65f3a91d6f3a4b0012ab34cd",
    "type": "reward_claim",
    "delta": -750,
    "createdAt": "2026-05-22T15:10:00.000Z"
  }
]

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404Unknown `userId`.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Redemptions

3 endpoints

A redemption claims an open auction at its `currentPrice`, deducting available points FIFO from the user's ledger. Pending (unsettled card-spend) points can't be redeemed. This is the most important client-side write — it's how a fan actually 'buys' a reward.

Create redemption

Claim an auction on behalf of the user. Pass both `rewardId` and `auctionId` — the server rejects mismatches. Merch redemptions additionally require `variantSku`.

POST/v1/users/{userId}/redemptions

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Request body

FieldTypeRequiredDescription
rewardIdstringrequired
auctionIdstringrequired
completedFormFieldsRecord<string, string>requiredValues for the reward's `formFields`. For experience, send `phone_number`; for merch/collectible, send name + address fields.
variantSkustringRequired for merch rewards. Must match a variant on the reward.
example body
{
  "rewardId": "65f3a91d6f3a4b0012ab50bb",
  "auctionId": "65f3a91d6f3a4b0012ab60cc",
  "completedFormFields": {
    "first_name": "Test",
    "last_name": "Fan",
    "phone_number": "555-555-1212"
  }
}

Response — 201

ReturnsRedemption

Click the type for the full field-by-field shape.

example response (201)
{
  "id": "65f3a91d6f3a4b0012ab80ff",
  "rewardId": "65f3a91d6f3a4b0012ab50bb",
  "userId": "65f3a91d6f3a4b0012ab34cd",
  "pointsSpent": 750,
  "completedFormFields": {
    "first_name": "Test",
    "last_name": "Fan",
    "phone_number": "555-555-1212"
  },
  "createdAt": "2026-05-22T15:10:00.000Z"
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404Unknown `userId` or `auctionId`.
  • VALIDATION_ERROR400`rewardId` doesn't match the auction's reward, or `variantSku` missing on a merch redemption.
  • AUCTION_CLOSED400Auction is paused, cancelled, or past its end date.
  • INSUFFICIENT_POINTS400User balance is below `currentPrice`.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

List user redemptions

Every redemption belonging to the user, newest first.

GET/v1/users/{userId}/redemptions

Path parameters

FieldTypeRequiredDescription
userIdstringrequiredUptop user id returned from `POST /users:upsert`.

Response — 200

Click the type for the full field-by-field shape.

example response (200)
[
  {
    "id": "65f3a91d6f3a4b0012ab80ff",
    "rewardId": "65f3a91d6f3a4b0012ab50bb",
    "userId": "65f3a91d6f3a4b0012ab34cd",
    "pointsSpent": 750,
    "completedFormFields": {
      "first_name": "Test",
      "last_name": "Fan",
      "phone_number": "555-555-1212"
    },
    "createdAt": "2026-05-22T15:10:00.000Z"
  }
]

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404Unknown `userId`.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Get redemption

A single redemption by id.

GET/v1/redemptions/{redemptionId}

Path parameters

FieldTypeRequiredDescription
redemptionIdstringrequiredRedemption id.

Response — 200

ReturnsRedemption

Click the type for the full field-by-field shape.

example response (200)
{
  "id": "65f3a91d6f3a4b0012ab80ff",
  "rewardId": "65f3a91d6f3a4b0012ab50bb",
  "userId": "65f3a91d6f3a4b0012ab34cd",
  "pointsSpent": 750,
  "completedFormFields": {
    "first_name": "Test",
    "last_name": "Fan",
    "phone_number": "555-555-1212"
  },
  "createdAt": "2026-05-22T15:10:00.000Z"
}

Errors

  • UNAUTHORIZED401Missing or invalid `Api-Key`.
  • NOT_FOUND404Unknown `redemptionId`.

Try it (curl)

curl
Set an API key in the top-right API settings menu —$UPTOP_API_KEY in the curl will be substituted for it on send.

Edit anything above, then send through the local proxy.

Type definitions

Every response above references one of these types. Each definition lists the full field-by-field shape — click any type name elsewhere on the page to jump back here.

An Uptop user. The `id` is what every other endpoint takes as `{userId}`. Returned by `POST /users:upsert`.

FieldTypeRequiredDescription
idstringrequiredUptop user id.
emailstring (email)requiredPrimary identifier. Upsert is idempotent on this.
phoneNumberstring (E.164)Present only if supplied on a previous upsert. **Required for card issuance** — pass it on first upsert or the user cannot receive a virtual card.
externalUserIdstringPartner-side identifier. Present only if supplied on a previous upsert.
firstNamestringPresent only if supplied on a previous upsert. **Required for virtual card issuance.**
lastNamestringPresent only if supplied on a previous upsert. **Required for virtual card issuance.**
createdAtstring (ISO 8601)required
example
{
  "id": "65f3a91d6f3a4b0012ab34cd",
  "email": "fan@example.com",
  "phoneNumber": "+12025551234",
  "externalUserId": "partner-user-001",
  "firstName": "Taylor",
  "lastName": "Fan",
  "createdAt": "2026-05-22T15:00:00.000Z"
}

WalletTransaction

#type-WalletTransaction

A single wallet credit or debit. `use_balance` transactions are debits and carry a negative `amount`.

FieldTypeRequiredDescription
idstringrequiredTransaction ID.
typestringrequiredTransaction type. Common values: `add_balance`, `use_balance`, `merchant_refund`.
amountintegerrequiredCents. Negative for `use_balance` (debit); positive for credits.
currencystringrequiredLowercase ISO 4217, e.g. `"usd"`.
createdAtstring (ISO 8601)requiredUTC timestamp.
descriptionstringHuman-readable label. Omitted if not set.
example
{
  "id": "txn-abc123",
  "type": "use_balance",
  "amount": -1500,
  "currency": "usd",
  "createdAt": "2026-06-01T18:30:00Z",
  "description": "Spend"
}

Extends `User` with a live wallet balance. Returned by `GET /users/{userId}`.

FieldTypeRequiredDescription
idstringrequired
emailstring (email)required
phoneNumberstring (E.164)Present only if supplied on a previous upsert. **Required for card issuance.**
externalUserIdstringPresent only if supplied on a previous upsert.
firstNamestringPresent only if supplied on a previous upsert. **Required for virtual card issuance.**
lastNamestringPresent only if supplied on a previous upsert. **Required for virtual card issuance.**
createdAtstring (ISO 8601)required
balance.amountnumberrequiredWallet balance in the **smallest currency unit** (cents for USD). Divide by 100 to display dollars.
balance.currencystringrequiredLowercase ISO 4217 currency code, e.g. `"usd"`.
example
{
  "id": "65f3a91d6f3a4b0012ab34cd",
  "email": "fan@example.com",
  "phoneNumber": "+12025551234",
  "externalUserId": "partner-user-001",
  "firstName": "Taylor",
  "lastName": "Fan",
  "createdAt": "2026-05-22T15:00:00.000Z",
  "balance": {
    "amount": 2500,
    "currency": "usd"
  }
}

Display-safe virtual card details. Does not include the full card number or CVV.

FieldTypeRequiredDescription
idstringrequiredVirtual card UUID.
typestringrequiredAlways `"VIRTUAL"`.
cardNetworkstringrequiredCard network, e.g. `"mastercard"`.
lastFourstringrequiredLast four digits of the card number.
expMonthstringrequiredZero-padded expiry month, e.g. `"08"`.
expYearstringrequiredFour-digit expiry year, e.g. `"2029"`.
cardHolderNamestringrequiredCardholder name on the virtual card.
statestringrequiredCard state, e.g. `"OPEN"`.
example
{
  "id": "47d688b2-c0d6-46a2-81e5-2baa979a5aa2",
  "type": "VIRTUAL",
  "cardNetwork": "mastercard",
  "lastFour": "8414",
  "expMonth": "08",
  "expYear": "2029",
  "cardHolderName": "Tony Lee",
  "state": "OPEN"
}

PaymentMethod

#type-PaymentMethod

A display-safe funding source attached to a wallet (a card or bank account used to load balance). Never includes the full account number. Returned by `GET /users/{userId}/wallet/payment-methods`.

FieldTypeRequiredDescription
idstringrequiredPayment method ID. Pass as `paymentMethodId` to add-balance to charge a specific source.
typestringFunding source type, e.g. `"card"` or `"bank_account"`. Omitted when unknown.
brandstringFor cards, the network (e.g. `"visa"`); for bank accounts, the bank name. Omitted when unknown.
last4stringLast four digits of the card or account number. Omitted when unknown.
expMonthintegerCard expiry month (`1`–`12`). Cards only; omitted for bank accounts.
expYearintegerFour-digit card expiry year. Cards only; omitted for bank accounts.
preferredbooleanrequired`true` for the default source used by add-balance when no `paymentMethodId` is given.
example
{
  "id": "pm-9f2c1a7b",
  "type": "card",
  "brand": "visa",
  "last4": "4242",
  "expMonth": 12,
  "expYear": 2027,
  "preferred": true
}

WithdrawableBalance

#type-WithdrawableBalance

The wallet balance alongside the portion of it a withdrawal will accept right now, returned by `GET /users/{userId}/wallet/withdrawable-balance`. Both figures are read at the same instant.

FieldTypeRequiredDescription
balanceintegerrequiredWallet balance in cents. An upper bound on `withdrawableBalance`.
withdrawableBalanceintegerrequiredCents a withdrawal will accept right now, bounded by the per-withdrawal minimum and maximum. `0` means none can be withdrawn.
currencystringrequiredLowercase ISO 4217. Always `"usd"` today.
reasonstringWhy `withdrawableBalance` is below `balance`. Absent when the two are equal. `open_card_hold`, `held_recent_load`, `non_cash_funds`, `maximum_withdrawal`, or `below_minimum_withdrawal` today; further values may be added, so treat an unrecognized value as a generic explanation.
example
{
  "balance": 15336,
  "withdrawableBalance": 13336,
  "currency": "usd",
  "reason": "held_recent_load"
}

An ACH credit from the wallet to a linked bank account, returned by `POST /users/{userId}/wallet/withdrawals`. The status is always `pending` on creation; the credit settles asynchronously and its outcome appears in `GET /users/{userId}/wallet/transactions`.

FieldTypeRequiredDescription
transactionIdstringrequiredWallet transaction id for the debit. Use it to match this withdrawal against the transaction list.
status"pending"requiredAlways `pending` on creation. Settlement is reported later in `GET /users/{userId}/wallet/transactions`.
amountCentsintegerrequiredCents debited from the wallet.
paymentMethodIdstringrequiredThe bank account the funds were sent to.
balanceintegerrequiredWallet balance in cents after the debit.
currencystringrequiredLowercase ISO 4217. Always `"usd"` today.
createdAtstringrequiredISO-8601 timestamp of the debit.
example
{
  "transactionId": "txn-7b21c4de",
  "status": "pending",
  "amountCents": 5000,
  "paymentMethodId": "pm-3c8b1e94",
  "balance": 10336,
  "currency": "usd",
  "createdAt": "2026-08-24T15:04:00.000Z"
}

A brand whose spend triggers points. Configuration is dashboard-driven.

FieldTypeRequiredDescription
idstringrequired
namestringrequired
descriptionstringrequired
logoUrlstring (url)required
primaryColorstring (hex)required
secondaryColorstring (hex)required
matchingType"plaid" | "receipt" | "combination"requiredHow spend is matched. `combination` accepts both linked-card and receipt evidence.
pointsMultipliernumberrequiredServer-side reward multiplier on matched spend.
frontendMultipliernumberrequiredDisplay-only multiplier rendered in the app.
prioritynumberrequiredHigher values surface earlier in the sponsor strip.
unavailablebooleanrequiredWhen true, hide the sponsor without deleting it. Unavailable sponsors never match spend — no `pointsMultiplier` boost, no `sponsorId` attribution on earns.
createdAtstring (ISO 8601)required
example
{
  "id": "65f3a91d6f3a4b0012ab40aa",
  "name": "Acme Coffee",
  "description": "Local coffee partner",
  "logoUrl": "https://example.com/acme.png",
  "primaryColor": "#1D428A",
  "secondaryColor": "#FFFFFF",
  "matchingType": "receipt",
  "pointsMultiplier": 2,
  "frontendMultiplier": 2,
  "priority": 10,
  "unavailable": false,
  "createdAt": "2026-05-22T15:00:00.000Z"
}

A redeemable item (ticket, experience, merch, collectible). Rewards carry no `pointsCost` — price comes from the auction. `merch` is only present when `type === 'merch'`.

FieldTypeRequiredDescription
idstringrequired
namestringrequired
descriptionstringrequired
iconUrlstring (url)required
buttonColorstring (hex)required
type"ticket" | "experience" | "merch" | "collectible"required
formFieldsFormField[]requiredFields the user must fill in to redeem this reward.
merch{ variants: MerchVariant[] }Present only when `type === 'merch'`. Pass the chosen `variantSku` on redemption.
tagsstring[]requiredFree-form editorial labels (e.g. `summer-drop`, `edc-exclusive`) for grouping/featuring beyond `type`. Always an array — `[]` when none are set.
createdAtstring (ISO 8601)required
example
{
  "id": "65f3a91d6f3a4b0012ab50bb",
  "name": "Home jersey",
  "description": "Replica home jersey",
  "iconUrl": "https://example.com/j.png",
  "buttonColor": "#FFFFFF",
  "type": "merch",
  "formFields": [
    {
      "name": "first_name",
      "type": "text",
      "required": true
    },
    {
      "name": "last_name",
      "type": "text",
      "required": true
    }
  ],
  "merch": {
    "variants": [
      {
        "sku": "home-jersey-M",
        "color": "blue",
        "size": "M",
        "inventory": 5
      }
    ]
  },
  "tags": [
    "fan-favorite",
    "summer-drop"
  ],
  "createdAt": "2026-05-22T15:00:00.000Z"
}

A single input the user has to provide to claim a reward.

FieldTypeRequiredDescription
namestringrequiredField key (e.g. `first_name`, `phone_number`).
type"text" | "select" | "email"required
requiredbooleanrequired
example
{
  "name": "phone_number",
  "type": "text",
  "required": true
}

MerchVariant

#type-MerchVariant

A purchasable SKU on a merch `Reward`.

FieldTypeRequiredDescription
skustringrequiredVariant SKU. Pass this as `variantSku` on a merch redemption.
colorstringrequired
sizestringrequired
inventorynumberrequiredRemaining stock.
example
{
  "sku": "home-jersey-M",
  "color": "blue",
  "size": "M",
  "inventory": 5
}

A Dutch-style auction pricing a `Reward` in points. `currentPrice` is `null` when the auction is `ended` or `cancelled`.

FieldTypeRequiredDescription
idstringrequired
rewardIdstringrequiredReward being auctioned.
rewardRewardFull reward object. Present only when the request used `?expand=reward`; omitted if the underlying reward no longer exists.
startPricenumberrequiredPoints cost at `startDate`.
reservePricenumberrequiredFloor the auction decays toward.
currentPricenumber | nullrequiredComputed at read time for `open`/`paused` auctions. `null` for `ended`/`cancelled`.
startDatestring (ISO 8601)required
endDatestring (ISO 8601)required
quantitynumberrequiredTotal claimable units.
quantityPurchasednumberrequired
status"open" | "paused" | "cancelled" | "ended"required
createdAtstring (ISO 8601)required
example
{
  "id": "65f3a91d6f3a4b0012ab60cc",
  "rewardId": "65f3a91d6f3a4b0012ab50bb",
  "startPrice": 1000,
  "reservePrice": 100,
  "currentPrice": 750,
  "startDate": "2026-05-22T14:00:00.000Z",
  "endDate": "2026-05-22T16:00:00.000Z",
  "quantity": 5,
  "quantityPurchased": 1,
  "status": "open",
  "createdAt": "2026-05-22T13:50:00.000Z"
}

PointsBalance

#type-PointsBalance

Current and lifetime points totals for a user.

FieldTypeRequiredDescription
userIdstringrequired
currentBalancenumberrequiredSpendable (available) points right now.
pendingBalancenumberrequiredPoints from card taps awaiting settlement — not yet redeemable. Estimated from the authorized amount; the amount credited to `currentBalance` reflects what actually settles, so it can differ (a fully reversed charge earns none).
lifetimeEarnednumberrequiredTotal points credited over the account's life. Decreases only when an earn is reversed — a refunded wallet purchase, or a clawback of points awarded for funds that were later withdrawn.
example
{
  "userId": "65f3a91d6f3a4b0012ab34cd",
  "currentBalance": 4250,
  "pendingBalance": 750,
  "lifetimeEarned": 5000
}

An immutable ledger entry. The four public `type` values are exposed by name; all internal event types (referrals, badges, wallet spend, …) collapse to `'other'`.

FieldTypeRequiredDescription
idstringrequired
userIdstringrequired
type"adjustment" | "receipt" | "daily_claim" | "reward_claim" | "spend" | "other"required`spend` is a card-spend earn.
deltanumberrequiredPositive credits the balance; negative debits it.
reasonstringFree-form. Typically only set on `adjustment` events.
sponsorIdstringThe sponsor whose spend triggered the earn, for theming Tokens. Set on sponsor-attributed card earns; absent otherwise.
citystringMerchant city where a card-spend (`spend`) earn occurred. Set when the card event carries location; absent otherwise.
statestringMerchant state/region for a card-spend (`spend`) earn. Set when the card event carries location; absent otherwise.
createdAtstring (ISO 8601)required
example
{
  "id": "65f3a91d6f3a4b0012ab70dd",
  "userId": "65f3a91d6f3a4b0012ab34cd",
  "type": "adjustment",
  "delta": 5000,
  "reason": "manual_seed",
  "createdAt": "2026-05-22T15:01:00.000Z"
}

A claimed auction. `pointsSpent` is the auction's `currentPrice` at the moment of claim — not the reward's nominal price.

FieldTypeRequiredDescription
idstringrequired
rewardIdstringrequired
userIdstringrequired
pointsSpentnumberrequiredAuction `currentPrice` at claim time.
completedFormFieldsRecord<string, string>requiredValues the user submitted for the reward's `formFields`.
createdAtstring (ISO 8601)required
example
{
  "id": "65f3a91d6f3a4b0012ab80ff",
  "rewardId": "65f3a91d6f3a4b0012ab50bb",
  "userId": "65f3a91d6f3a4b0012ab34cd",
  "pointsSpent": 750,
  "completedFormFields": {
    "first_name": "Test",
    "last_name": "Fan",
    "phone_number": "555-555-1212"
  },
  "createdAt": "2026-05-22T15:10:00.000Z"
}