Changelog

A dated record of changes to the v1 API and these docs, newest first.

  1. Choose the API environment from the docs

    • A Dev / Production switch in the top bar now decides which API the Try it panels, the Simulate tab, the Dashboard tab, and the webhook test firer talk to, and the host shown on the Docs overview and in every curl sample follows it. A Local option appears only when the docs themselves run on localhost. The selection is remembered in your browser, and the API key and User ID in API settings are kept separately per environment, since dev and production issue different keys and hold different users.
    • The hosted Add Funds and Withdraw Funds iframes accept an optional env=dev query parameter alongside userId and iframeToken, so a development build of your app can point the embeds at the dev API. Without the parameter the embeds keep talking to production, so existing integrations are unaffected.
    • Transaction simulation is disabled in production by design, so with Production selected the Simulate tab says so instead of failing.
  2. Merchant identifier on card transactions

    • transaction.created webhooks now carry merchantId, the card network's merchant identifier (acceptor ID) for the transaction, passed through exactly as the network reported it. It sits alongside descriptor and mcc, and is null when the network did not provide one, so the key set stays stable.
  3. Withdrawals deduct points awarded for recently added funds

    • Withdrawing wallet funds that were added recently now deducts the points that were awarded for adding them. The deduction covers only the portion of the withdrawal that recent loads back — withdraw part of a recent load and only that part's bonus is removed — and one load is never deducted twice across multiple withdrawals. Funds that are added and then spent in-venue are unaffected; only cash-outs of recently added funds trigger it.
    • The deduction appears in GET /v1/users/{userId}/points/events as a negative-delta entry of type other, and a user's points balance can go negative when the awarded points were already redeemed. lifetimeEarned comes down by the same amount, as it already does when a wallet purchase is refunded.
    • The lookback window is a fraud control and is not disclosed, matching how funds-availability holds are documented. No request or response shapes change, and no integration work is required.
  4. Withdrawable balance endpoint

    • GET /v1/users/{userId}/wallet/withdrawable-balance returns how much of a wallet balance a withdrawal will actually accept, alongside the wallet balance itself. Bind any "Max" control to withdrawableBalance rather than to balance: the two differ whenever recently added funds are still processing, a card purchase is pending, or part of the balance is promotional credit. See Docs → Wallet and the Withdrawals guide.
    • Both figures come from a single read, so they cannot disagree. On a withdrawal screen this endpoint replaces GET /v1/users/{userId}/wallet rather than supplementing it.
    • When withdrawableBalance is below balance, a reason field explains the shortfall: open_card_hold (withdrawals blocked until the purchase finalizes, and the withdrawable figure is 0), held_recent_load (funds still processing), non_cash_funds (promotional credit, never withdrawable), maximum_withdrawal (the balance exceeds the per-withdrawal maximum, so the figure is that maximum), or below_minimum_withdrawal (what remains is under the per-withdrawal minimum, so the figure is 0). One reason is reported when several apply, whichever determines the figure. Treat an unrecognized value as a generic explanation, since further values may be added.
    • The response carries no clearing dates and no per-reason amounts, and integrations should not infer them: the holding period is a fraud control. Wording such as "some funds are still processing" tells a user what they need to know without exposing it.
    • The per-withdrawal minimum and maximum are applied to the figure, so a control bound to withdrawableBalance cannot produce an out-of-bounds amount and no integration needs to hardcode either limit. The figure is still a snapshot rather than a reservation, so keep the INSUFFICIENT_FUNDS path and re-read the balance before offering a retry.
    • The hosted Withdraw Funds embed now shows the withdrawable balance as its headline figure and binds Max to it, with any shortfall explained beneath. Where withdrawals are not configured for a program, both the endpoint and the embed report that directly rather than failing.
  5. Withdrawals are a published API endpoint

    • POST /v1/users/{userId}/wallet/withdrawals is now part of the documented v1 contract, callable server-to-server with your partner Api-Key as well as with a user-scoped client token. You can build your own withdrawal screen against it instead of embedding the hosted one; the embed calls the same endpoint, so the two are alternatives rather than layers. See Docs → Wallet and the new Withdrawals tab.
    • New Withdrawals guide covering the integration end to end: listing bank destinations, amount rules, idempotency and retries, each refusal code and whether the wallet was debited, and how to match settlement against GET /v1/users/{userId}/wallet/transactions.
    • idempotencyKey (a UUID) is required on every withdrawal request. Generate one per withdrawal intent, meaning the amount and destination the user confirmed, and send that same key on every retry: a replay returns the original transaction instead of submitting a second ACH credit. Reusing a key with a different amount returns 409. A timeout or a 502 is an unknown outcome rather than a failure, so retry with the same key rather than creating a new withdrawal.
    • Destinations must be linked bank accounts, because withdrawals move by ACH; a card paymentMethodId is rejected with VALIDATION_ERROR. Two error codes are new in the shared error table: INSUFFICIENT_FUNDS (402) and WITHDRAWAL_LIMIT_EXCEEDED (400).
    • Withdrawal behavior is unchanged; this release exposes and documents what the embed already did. Withdrawals are configured per program: if a request returns FORBIDDEN, contact your account contact.
  6. Withdraw Funds embed

    • New hosted iframe at /embed/withdraw lets a fan send wallet cash back out to a linked bank account by ACH. Same integration as Add Funds — userId and iframeToken on the query string, your app renders the screen title and exit button — so one client-token fetch covers both screens. See the Embed tab.
    • Destinations are linked bank accounts only, because withdrawals move by ACH; cards are not offered. A fan with no linked bank is prompted to link one. Link banks from your app with Plaid's native SDK, as with Add Funds.
    • Withdrawals confirm as Pending, not complete: the money typically lands in 1–2 business days, and the outcome appears in GET /v1/users/{userId}/wallet/transactions. No fee.
    • The screen shows the wallet balance as available, but the amount actually withdrawable can be lower — recently added funds are held until they clear, and a pending card purchase blocks withdrawal entirely. When a withdrawal is refused the screen explains why rather than failing generically.
    • Withdrawals are off until enabled for your program. Contact Uptop to turn them on.
  7. Add Funds embed no longer launches Plaid Link

    • The "Link a Bank Account" buttons are gone from the Add Funds iframe. Apps launch Link through Plaid's native SDK, so the in-iframe launcher was a redundant second path to the same thing. The embed still handles amount selection, adding a card, and confirmation.
    • Bank linking itself is unchanged and fully supported — this only removes the iframe's own entrypoint into Link. POST /v1/users/{userId}/wallet/link-token and POST /v1/users/{userId}/wallet/bank-accounts behave exactly as before, and a bank linked through them still appears in the embed as a selectable payment method and funds loads as usual.
  8. Raw transaction data via webhook

    • New transaction.created webhook event: every approved card transaction attributable to one of your users now POSTs its raw card data — amount, currency, network descriptor, MCC, merchant city/state, and stable transaction ids — to your configured webhook endpoint, including transactions that award no points. Earning transactions still emit points.pending; join the two with pointsEventId. Same endpoint, secret, and signature scheme as points.pending — nothing to reconfigure. Payload reference is on the Webhooks tab.
  9. Bank linking is now a published v1 endpoint pair

    • Two new endpoints cover Pay-by-Bank setup end to end: POST /v1/users/{userId}/wallet/link-token mints a Plaid Link token, and POST /v1/users/{userId}/wallet/bank-accounts exchanges the public token from Link for a bank payment method on the wallet. Both accept either the partner Api-Key or a user-scoped client token from POST /v1/users/{userId}/auth/client-token. See Docs → Wallet.
    • Mobile apps can now run Plaid Link in-process with Plaid's native SDK instead of opening Hosted Link in a browser: pass platform: "ios" | "android" when creating the link token. Omit platform for web, and note that platform and hosted are mutually exclusive.
    • The platform matters because Plaid identifies your app differently on each: iOS uses an https Universal Link (redirect_uri), Android uses your applicationId (android_package_name), and the two cannot be combined on one token. Send Uptop both values so we can register them in the funding Plaid Dashboard — without them, OAuth banks (e.g. Chase) cannot hand control back to your app.
    • Hosted Link is unchanged and stays fully supported.
  10. Unavailable sponsors stop affecting card-spend earns

    • Disabling a sponsor (unavailable: true) now takes it fully out of card-spend earning: spend at that merchant still earns at the base rate, but no longer gets the sponsor's pointsMultiplier boost or sponsorId attribution on the points event. This matches how linked-card matching already treated unavailable sponsors. See the unavailable field under Docs → Sponsors.
  11. Add Funds embed redesign

    • The hosted Add Funds iframe has a new dark look to match the updated app designs: current wallet balance on the amount screen, radio-style payment method selection with a Continue confirmation, and a live card preview on Add Card that mirrors the detected brand, last four, and expiry as the fan types. Card capture behavior is unchanged — the card number and CVC never leave the tokenization iframes. No integration changes required; the embed URL and parameters are the same. See the Embed tab for the updated preview.
    • Add Card now collects an optional billing ZIP and forwards it to the payment processor for AVS verification.
  12. Simulate card transactions

    • New Simulate tab: drive the card-spend earn flow end to end without a live card or a real Ansa delivery. Pick a cardholder, simulate an authorization at any merchant, then settle it — the synthetic event runs the same handlers a real Ansa webhook would, so you can watch pending points become available, verify sponsor theming and multipliers, and exercise your points.pending receiver. Sandbox only.
  13. List wallet payment methods

    • GET /v1/users/{userId}/wallet/payment-methods returns the display-safe funding sources attached to a wallet — brand, last four, and expiry per card, plus which is preferred. Use it to show saved cards before prompting a load. See Docs → Wallet.
  14. Merchant location on card-spend earns

    • Card-spend point events now include the merchant city and state where the fan spent — populated from the card transaction, absent when the event carries no location. Use it to show or group earns by where the fan spent. See the points event shape under Docs → Points.
  15. Card-spend earning

    • Card-spend point events now include a sponsorId when the merchant matches a sponsor, so you can theme Tokens by where the fan spent. See the points event shape under Docs → Points.
    • Card taps now earn pending points that become available (redeemable) once the charge settles — credited at the amount that actually settled, so a tip or partial capture adjusts the award and a fully reversed charge earns none. The points balance now returns both currentBalance (available) and pendingBalance (estimated from authorization); pending points can't be redeemed. See Docs → Points balance.
    • Uptop now POSTs a points.pending event the moment a fan earns, so your app updates without polling. Setup, payload, signature verification, and a test-fire tool are on the new Webhooks tab.
  16. Free-form reward tags

    • Rewards now carry an optional tags[] array for editorial grouping (e.g. edc-exclusive, summer-drop) beyond the fixed type — surfaced on the v1 reward shape.
  17. Expand reward on auctions

    • GET /v1/auctions?expand=reward inlines the full reward object on each auction row, removing the extra per-item call your storefront used to make.
  18. Phone number on upsert

    • POST /v1/users:upsert accepts phoneNumber (E.164), required for virtual-card issuance.
  19. Wallet API

    • Wallet endpoints: get wallet + live balance, cursor-paginated transactions, attach payment method, add balance, and display-safe virtual-card metadata.
  20. v1 API launch

    • Initial v1 surface: users, sponsors, rewards, auctions, points, and redemptions — all keyed by the Uptop user id returned from users:upsert.