# Simulate Virtual Card Transactions Source: https://docs.fluz.app/Simulate-Virtual-Card-Transactions Put a test spend on a staging virtual card — how to request an authorization, clearing, decline, or refund, and how to verify the result over the API. Staging virtual cards are real card records with real BINs, but they aren't on a live payment network — no merchant can swipe them. To exercise your spend, balance, and reconciliation logic, Fluz injects an authorization against your card through the issuer processor's test environment. Everything downstream of that point is the production code path: the same authorization service, the same spend controls, the same ledger entries, the same webhooks. **Staging only** Simulated transactions exist only in the staging environment (`https://transactional-graph.staging.fluzapp.com/api/v1/graphql`). No funds move, no interchange is generated, and nothing is submitted to Mastercard. In production, transactions arrive only from real merchant activity. **Simulations are triggered by Fluz** There is no public mutation that injects a transaction onto a card. Authorization simulation happens in the issuer processor's dashboard, which sits inside Fluz's PCI environment and isn't exposed to partners. Request the transactions you need through your integration channel (shared Slack channel or [partnerships@fluz.app](mailto:partnerships@fluz.app)) and we'll run them against your card, usually the same business day. Everything else on this page — issuing the card, reading the result — is fully self-serve. ## Before you request a simulation An authorization runs through the full control stack, so a card that isn't set up correctly will decline for reasons that have nothing to do with your test. Cards can only be issued — and only authorize — on a verified account. See [Testing KYC Flows](/test-kyc-flows) for identities that return a pass. Issue one with `createVirtualCard` using an offer from [Test Virtual Card Offers](/test-virtual-card-offers). Hold on to the `virtual_card_id` — it's how we locate the card. The card draws on your Fluz balance. Confirm the spend limit covers your test amount and that the card hasn't been locked, expired, or spent down. `getVirtualCardBalance` shows you `remainingBalance` at a glance. ## What you can simulate Ask for whichever leg of the lifecycle you need. Each maps to a distinct set of records and webhooks on your side. The base case: a purchase at a merchant you name, for an amount you name. The card's available balance is reduced immediately and the transaction lands in a pending state. Use this to verify that spend controls, balance decrementing, and your `TRANSACTION_CREATE` handler all behave. The settlement leg that follows an authorization, sometimes days later in the real world. We can either capture in a single step alongside the authorization, or leave the authorization open so you can observe the pending state and then request the capture separately. The second option is the more faithful rehearsal of production. A transaction the authorization service rejects. Tell us which decline you want to see — a control-driven decline (over the spend limit, wrong merchant on a brand-locked card, locked card) or an authentication decline (CVV mismatch). Declines surface a `declineReason` and `declineCategory`; see [Decline Codes](/features/decline-codes) for the full set. An authorization released before it clears — the merchant abandoned the sale, or the terminal timed out. The held amount returns to the card. Worth testing if you reconcile on authorizations rather than clearings. Value returned to the card after a purchase has cleared, in full or in part. Appears as a `REFUND` transaction type rather than a reduction of the original purchase, so your ledger needs to handle it as a separate record. Some merchants probe a card with a $0.00 or $0.01 authorization before charging it. These appear as their own records and are reversed shortly after. If your reconciliation sums authorizations, test this case — it's a common source of double-counting. ## What to send us The more of this you provide, the fewer round trips. | Field | Required | Notes | | -------------------- | -------- | ------------------------------------------------------------------------ | | `virtual_card_id` | Yes | Returned by `createVirtualCard`. Card last four also works. | | Amount | Yes | In USD. | | Outcome | Yes | Approve or decline — and if decline, which reason you want to exercise. | | Merchant name | No | Defaults to a generic test merchant. Set it if you match on descriptors. | | MCC | No | Set it if you're testing category-based logic. | | Single-step clearing | No | On to capture immediately; off to leave the authorization pending. | ## Verifying the result Once we confirm the simulation has run, everything is readable over the API. Nothing about reading a simulated transaction differs from reading a real one. ```graphql Transactions on the card theme={null} query { getVirtualCardTransactions(input: { virtualCardIds: [""] filters: { transactionTypes: [PURCHASE, REFUND, DECLINE] } }) { virtualCardId transactions { transactionId transactionDate transactionType transactionStatus transactionAmount transactionApproval transactionResponseCode merchantName merchantDescriptor mcc } } } ``` ```graphql Balance after the spend theme={null} query { getVirtualCardBalance(input: { virtualCardIds: [""] }) { virtualCardId spentAmount remainingBalance spendLimit spendLimitDuration } } ``` A simulated purchase should show up as a `PURCHASE` row with a matching drop in `remainingBalance`. A decline shows up under the `DECLINE` type and leaves the balance untouched — declines are also queryable in isolation through [Get Declined Transactions](/features/get-decline-transactions). Card-level queries only cover card activity. To see the same event in the account's unified ledger alongside deposits and transfers, use `getTransactions` — see [Transactions Overview](/features/transactions-details-overview). ### Webhooks Simulated transactions fire the same events as real ones, which makes this the cleanest way to test your endpoint end to end: | Event | Fires when | | --------------------- | -------------------------------------------------------------------------- | | `TRANSACTION_CREATE` | The authorization is approved. `status` is `PENDING`. | | `TRANSACTION_UPDATE` | The transaction clears. `status` moves to `SETTLED`. | | `TRANSACTION_DECLINE` | The authorization is rejected, with `declineReason` and `declineCategory`. | If you asked for an authorization without single-step clearing, you should see `TRANSACTION_CREATE` on its own and `TRANSACTION_UPDATE` only after the capture is run. See [Webhooks](/fluz-dashboard/webhooks) for payloads and signature verification. ## Troubleshooting | What you see | Likely cause | | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | Declined when you expected an approval | Spend limit below the amount, card locked, insufficient Fluz balance, or a brand-locked card at the wrong merchant. | | Nothing appears on the card | The simulation ran against a different card. Confirm the `virtual_card_id` you sent. | | Transaction stays pending | Expected — the authorization hasn't been captured. Request the clearing leg. | | No webhook received | Check the subscription and required scopes; the transaction itself is still visible over the API. | | Balance doesn't move on a \$0.01 charge | Expected for an AVS probe. It reverses on its own. | ## Next steps The full happy path — pick a program, issue a card, reveal it, and track its spend. Filter card activity by type and date range, and read every field on a transaction. Every decline reason and category, and what your app should do with each. Subscribe to transaction events, verify signatures, and handle retries. **Want to learn more?** Contact us at [partnerships@fluz.app](mailto:partnerships@fluz.app). Speak with our experts for more info or to request a demo. # API Explorer Source: https://docs.fluz.app/api-explorer # Catalog Fields and Descriptions Source: https://docs.fluz.app/catalog-fields ## Merchant Table | Field Name | Constraints | Description | Table | | ----------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `merchant_name` | Text | This is the name of the specific gift card merchant. In the event that you are calling for a catalog that will show both fixed and variable offers on the same merchant, there will be a unique record for each offer type on each merchant. | Merchant | | `merchant_slug` | Text | This is the shortened name for the merchant. It can be used for passing the purchases to Fluz via API for a specific merchant. It is also used to generate the merchant's URL on the Fluz web application. The URL structure is [https://fluz.app/store/merchant\_slug](https://fluz.app/store/merchant_slug) | Merchant | | `updated_at` | Date, time stamp | Last update made to that record on the merchant table | Merchant | | `created_at` | Date, time stamp | Date this merchant was added to our catalog | Merchant | | `merchant_status` | Active or Inactive | This will indicate if a merchant is available for purchase at this time. A merchant's status can either be active or inactive. If a merchant is archived it will be removed from the catalog export entirely. | Merchant | | `logo_url` | URL | Image of the merchant logo. This will be a square image of the merchant logo. It will be a vector image at 300 x 300 pixels. | Merchant | | `faceplate_image` | JSON | JSON obj of images of different sizes for the gift card. This will be the official images of the gift card as provided by the merchant. | Merchant | | `primary_color` | Hex Code | For merchandising within your experience, Fluz will provide you with the primary color in the merchant logo image. | Merchant | | `type` | Gift Card or Card Linked Offer | Fluz has both gift card offers and card linked offers. This is based on the specific merchant. Each merchant can have either option. This field will confirm the offer type as either a gift card offer or card linked offer. | Merchant | | `category` | List | This is the mapping of the merchant within the overall Fluz catalog. | Merchant | | `daily_limit-consumer` | Number | This is the total maximum dollar amount that can be purchased on a single merchant in a single day. This is by a single user account that is registered as a consumer account. A calendar day is run based off UTC time zones. The limits will reset daily. | Merchant | | `daily_limit-commercial` | Number | This is the total maximum dollar amount that can be purchased on a single merchant in a single day. This is by a single user account that is registered as a commercial account. A calendar day is run based off UTC time zones. The limits will reset daily. | Merchant | | `monthly_limit-consumer` | Number | This is the total maximum dollar amount that can be purchased on a single merchant in a single calendar month. This is by a single user account that is registered as a consumer account. A calendar month is run based off UTC time zones. It will run from the first of the month to the last day of the calendar month. The limits will reset monthly. | Merchant | | `monthly_limit-commercial` | Number | This is the total maximum dollar amount that can be purchased on a single merchant in a single calendar month. This is by a single user account that is registered as a consumer account. A calendar month is run based off UTC time zones. It will run from the first of the month to the last day of the calendar month. The limits will reset monthly. | Merchant | | `website_url` | URL | This is URL of that specific merchant. Please note, if the merchant has multiple URLs or sub domains, Fluz will be providing a single URL for that given merchant. | Merchant | | `redemption_information` | Text | These are details on how to redeem the gift card at that specific merchant. These can be specific | Merchant | | `parent_company` | Text | If the specific merchant's gift card is able to be used at other retail brands, we will list the name of the parent company that | Merchant | | `redeemable_at_other_brands` | List | What other merchants this gift card is redeemable at. Sometimes it will be other child merchants under the same parent merchant. But other times it will be an assortment of merchants for a multi brand card.

Examples:
Uber gift cards are redeemable at Uber + Uber Eats
Gap gift cards are redeemable at Gap + Old Navy + Banana Republic + Athleta
Happy Card Retail Therapy cards are redeemable at Lululemon, Macys, Nike, Boohoo, etc | Merchant | | `merchant_gift_card_redemption_url` | Text | If the merchant has a specific URL to apply the gift card balance directly to your account with that merchant, that specific URL will be provided in this field. | Merchant | | `description` | Text | This is the fully fleshed out description of the merchant. | Merchant | | `short_description` | Text | This is the shortened version of the description on that merchant. | Merchant | | `merchant_support_phone_number` | Phone Number | If the merchant provides a dedicated phone number address to support issues regarding their gift card program, that information will be provided here. | Merchant | | `merchant_support_email` | Email address | If the merchant provides a dedicated email address to support issues regarding their gift card program, that information will be provided here. | Merchant | ## Offer Table | Field Name | Constraints | Description | Table | | ----------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | `offer_id` | UUID | This is the public offer ID that you will reference | Offer | | `rate` | number | This is the best rate on the respective merchant.

If the fixed and variable have the same rate, then just include the variable.

If variable is less than fixed value, include both the variable and fixed value offers, unless you are calling the catalog that just has the variable value offers only.

In the event that the merchant has multiple different fixed value denomination offers with different rates, this will have a separate offer for each denomination + rate combo. | Offer | | `denomination_type` | Fixed or Variable | This field will be corresponding to the denomination of that particular offer. Each highest value offer will have their own record. | Offer | | `denominations` | number range | This field will be corresponding to the denomination of that particular offer.

**Variable Value Offers** - We will provide two values for denominations. The first value will be the lower limit of the offer. The second value will be the upper limit of the offer. When placing an order you can pass any value between that range.

**Fixed Value Offers** - We will list all denominations on that offer. Please note if there are different rates on a various fixed values of a merchant, each one will be a separate offer. In the respective offer, we will list the values on that specific rate. | Offer | | `card_format` | URL or code | Some of merchants only allow for a hosted URL for the gift card delivery. In that case, we will tell you the format you can expect. | Offer | | `barcode_type` | 128, UPC, QR, None | Some retailers require a barcode or QR code and some of these retailers require manipulation of the gift code to produce the barcode number, such as having a prefix, dropping some digits, or adding the PIN. To remove this complexity from our clients we return the barcode information in our Issue response data. | Offer | | `currency` | Text | This is the currency that the value on the gift card is tied to. | Offer | | `countries` | Country list | This is saying which countries the gift cards can be redeemed in

*Please note that the underlying value on each gift card will be tied to the currency listed on the gift card. Even if it works in other countries that generally operate in a different currency, the value will be tied to the listed currency.* | Offer | | `gift_card_expiration_window` | Number | This is the amount of months that the gift card expires in. This is based on the months from card issuance. If null, that means that the gift card does not expire. | Offer | | `merchant_terms` | Text | These are the terms of use provided by the merchant on the terms of use for the specific merchant's gift card program. | Offer |
# Changelog Source: https://docs.fluz.app/changelog Recent updates, additions, and breaking changes to the Fluz API. Subscribe to updates on the [Fluz Status Page](https://you-up.fluz.app/) for incidents and proactive change notifications. You can also subscribe to this changelog via [RSS](/changelog/rss.xml). * Open Loop Cards: recipients are no longer prompted to create a PIN during the initial activation flow, and the card is no longer auto-revealed on claim. Revealing a card now prompts the recipient to enter their PIN, or create one if they haven't set one yet. See [Recipient Experience](/features/open-loop-cards/open-loop-cards-recipient-experience). * Update GenerateVCShareLinksInput for Open Loop Cards. See [Open Loop Cards](/features/open-loop-cards/send-open-loop-cards). * Updates to [Primary & backup funding page](https://docs.fluz.app/features/primary-and-backup-funding). * New API endpoint exposing account-related declined transactions. See [Get Declined Transactions](/features/get-decline-transactions). * `GiftCard.purchaseId` (UUID) — the purchase that created the gift card; the same ID returned by `purchaseGiftCard` and `getUserPurchases`. Returns `null` if no associated purchase. * `GiftCard.purchaseDisplayId` (String) — the short, human-readable Fluz transaction ID (e.g. `1047283`) that support references in manual reviews and exports. Returns `null` if no associated purchase. * `GiftCard.purchaseValue` (Float) — the face value (denomination) the gift card was purchased at, in the card's currency. * `GiftCard.currentValue` (Float) — the remaining balance on the gift card; equals `purchaseValue` for single-use cards. * `GiftCard.currency` (String) — the ISO currency code of the gift card's value (e.g. `USD`). All five fields are now returned by `getGiftCards` (and anywhere `GiftCard` is exposed), enabling order/value reconciliation without calling `revealGiftCardByGiftCardId` per card. Additive and backward-compatible. * Added Plaid Link support. * Updated Overview page with `llms.txt` information. * Updated header with AI link to `llms.txt`. * New `getCardProvisioningUrl` **query** — mints a short, single-use URL that, when opened on a mobile device, launches Fluz and adds a virtual card to Apple Pay (iOS) or Google Pay (Android). Surface it as a QR code, SMS, email, or in-app button. Each URL expires \~5 minutes after creation. Requires the `CREATE_VIRTUALCARD` scope. * New input `GetCardProvisioningUrlInput { offerId, platform }`. * New enum `ProvisioningPlatform` (`IOS` | `ANDROID` | `OTHER`) — controls the desktop / unknown-device fallback only; iOS and Android opens are auto-routed. * New return type `CardProvisioningUrl { url, expiresAt }`. * Added samples to [Generate Share Links](/features/generate-share-links). * Added `addVirtualCardAddress` mutation to save billing addresses for virtual card issuance. * Updated `createVirtualCard` mutation to support creating cards on behalf of authorized users. * Added full flow using `registerUser`, `addAuthorizedUser`, `addVirtualCardAddress`, and `createVirtualCard`. * Retry pending issuer approvals with `VC-0020` and returned `addressId`. * `redeemFluzGiftCard` — Redeem a Fluz Gift Card using a code. Credits `giftCardCashBalance`. Requires `MAKE_DEPOSIT`. See [Redeem a Fluz Gift Card](/features/redeem-fluz-gift-card). * `unlockVirtualCard` — Unlock a previously locked virtual card to restore transaction capability. Requires `EDIT_VIRTUALCARD`. See [Unlock a Virtual Card](/features/unlock-virtual-card). **Transaction-related webhooks** * Added description for `TRANSACTION_CREATE`, `TRANSACTION_UPDATE` & `TRANSACTION_DECLINE` webhooks. * Included new fields after webhook enrichment (MCC-related, `virtualCardId`, etc.). * Added `mcc`, `merchantCountryCode`, `originalCurrencyCode`, `originalCurrencyAmount`, and `currencyConversionRate` to `VirtualCardTransaction`. Now returned by `getVirtualCardTransactions`. * Made `virtualCardIds` optional on `getVirtualCardTransactions` — omit to query transactions across all cards on the authenticated account. Added `dateRangeStart` and `dateRangeEnd` filters. Default `limit` of 100 when `virtualCardIds` is omitted; `limit` is capped at 500. `transactionAmount` is now nullable for rows without a settled amount (e.g., AVS-only decline records). * Added `usePrepaymentBalance` and `useRewardsBalance` to `CreateVirtualCardInput`. Now accepted by `createVirtualCard`. Both default to `true` (existing behavior preserved). Set both to `false` to restrict a card to drawing only from the specified `userCashBalanceId`, excluding prepaid (gift card) and rewards funds. * Added `shortDescription` (Merchant) and `termsAndConditions` (Offer). Now returned by `getMerchants` and `getOfferQuote`. * Added `deliveryFormat` (GiftCard). Now returned by `getGiftCards` — use `deliveryFormat` for purchased cards. * New `setVirtualCardPIN` mutation, allowing a user to set their PIN on eligible virtual cards. See [Set a Virtual Card PIN](/recipes/set-virtual-card-pin). * New `getUserCashBalances` & `getUserCashBalanceById` queries. These queries allow a user to retrieve a list of their spend accounts and a specific spend account created on their account. **Share Virtual Cards API** * New "Share Virtual Cards" capability under Manage Virtual Cards. Contact sales for access. * Added `CREATE_SHARE_LINK` scope to the [application scopes](/fluz-dashboard/application-scopes) page. **Business registration API** * New query `getBusinessCategories: [BusinessCategory]` for retrieving business categories, with response types `BusinessCategory` and `BusinessSubCategory`. * New mutation `registerBusiness(input: RegisterBusinessInput!): RegisterBusinessResult` for programmatic business registration. * New input types: `RegisterBusinessInput`, `BusinessLegalAddressInput`, `BusinessOwnerInput`, `OwnerAddressInput`. * New enums: `BusinessStructure` (`LLC`, `CORPORATION`, `PARTNERSHIP`, `SOLE_PROPRIETORSHIP`, `COOP`) and `BusinessAccountUsage`. * New response types: `RegisterBusinessResult { accountId, kybStatus, success, error }` and `RegisterBusinessError { message, code }`. * Both operations require authentication with the `REGISTER_BUSINESS` OAuth scope. **Updates to `getMerchants` query** * The `getMerchants` query now returns all available offers properly. * The `getMerchants` query can now return an `exclusiveRateId` field (within the `offers` object). This value can be used in `purchaseGiftCard` to specify the exclusive rate offer you want to purchase with. **Withdrawals & Spend Accounts APIs** * Added `withdrawCashBalance` mutation with `MAKE_WITHDRAWAL` scope requirement. * Added `WithdrawMethods` enum supporting `PAYPAL`, `BANK_ACH`, `BANK_CARD`, and `VENMO` methods. * Added `WithdrawSource` enum supporting `CASH_BALANCE` and `REWARDS_BALANCE` sources. * Added `WithdrawCashBalanceInput` with idempotency support, plus `Withdraw` and `WithdrawCashBalanceResponse` types with full withdrawal record details including fees, status, and timestamps. * Added `transferUserCashBalance` mutation with `MAKE_INTERNAL_TRANSFER` scope requirement, plus `TransferInternalBalanceInput` and `TransferInternalBalanceResponse` types. * Added `userCashBalanceId` to `EditVirtualCardInput` to enable changing the funding source to a specified spend account via `editVirtualCard`. * Added `userCashBalanceIds` to `TransactionFilterInput` for filtering transactions by spend accounts via `getTransactions`, and `userCashBalanceId` to `getGiftCards` for filtering gift cards by spend account. * Added `updateUserCashBalance` and `closeUserCashBalance` mutations with `MANAGE_PAYMENT` scope requirement, plus supporting input and response types (`UpdateUserCashBalanceInput`, `CloseUserCashBalanceInput`, `CloseUserCashBalanceResponse`, `ClosedUserCashBalance`, `CloseUserCashBalanceResponseVirtualCard`). * Added application action logging for audit trail. * Added new `userCashBalances` field to the `UserBalances` type in the `getWallet` query. Returns detailed information about individual user cash balance accounts with pagination via `paginate` (limit/offset), ordered by creation date (most recent first). Includes `userCashBalanceId`, `totalCashBalance`, `availableCashBalance`, `lifetimeCashBalance`, `nickname`, `status`, and `createdAt` — enabling tracking of multiple cash balance accounts per user with custom nicknames and status monitoring. * A new field `userCashBalanceId` is added to `DepositCashBalanceInput` to specify the cash balance (spend account) to deposit the funds into. * New mutations for managing saved bank cards: `updateBankCardNickname`, `updateBankCardPreferredMerchantCategoryCode`, and `deleteBankCard`, with corresponding input types. * `nickname` field added to the `BankCard` type. * Optional fields added to `AddBankCardInput`: `nickname` and `preferredMerchantCategoryCode` (MCC, normalized to 4 digits). * `getVirtualCardBalance`: new query retrieving balance information for a list of virtual cards, including `spentAmount`, `remainingBalance`, `spendLimit`, and `spendLimitDuration`. * `getVirtualCardTransactions`: new query retrieving transactions for a list of virtual cards, with filters such as `transactionTypes`. * The `getUserPurchases` query now includes the ability to filter the purchases returned in the response via a new optional `UserPurchaseFilterInput` variable. Filter purchases made by the user, the account, or both. * The `revealVirtualCardByVirtualCardId` mutation response now includes a new `authorizationSetting` field. * **Bulk Virtual Card API**: new API for creating and managing a large number of virtual cards asynchronously. * `createVirtualCardBulkOrder` **mutation**: submit a bulk order and receive a unique `orderId`. * `getVirtualCardBulkOrderStatus` **query**: poll to track order status and retrieve card details upon completion. * The bulk card creation process is asynchronous — check order status to retrieve card details. * **New query `getVirtualCardOffers`**: comprehensive list of all active virtual card offers available for creation, including `offerId` (essential for card creation), `programName`, `bankName`, `rewardValue`, and detailed program limits (daily, weekly, monthly spend limits). * **Updated mutation `createVirtualCard`**: programmatically issue virtual cards by providing an `offerId` (from `getVirtualCardOffers`), a `spendLimit`, and customizable attributes like `cardNickname` and `spendLimitDuration`. * **New mutation `editVirtualCard`**: update key parameters of an existing virtual card — `spendLimit`, `spendLimitDuration`, `lockDate`, `lockCardNextUse`, and `cardNickname`. * **New mutation `lockVirtualCard`**: immediately lock a virtual card, preventing any further transactions. * The `Offer` type in the `getMerchants` response now includes a `deliveryFormat` field specifying how the offer is fulfilled: `URL`, `CODES`, `PIN_AS_CODE`, or `PIN_WITH_URL`. * The `getMerchants` query has been enhanced with a new `filterBy` argument, allowing you to filter results to only include merchants with offers matching a specific `deliveryFormat`. * Enhanced the `getMerchants` query with a new `offerTypes` input argument, allowing filtering by `giftCardOffer`s, `cardLinkedOffer`s, or both. By default, if `offerTypes` is not provided, the query returns only gift card offers. * Introduced a new query `getReferralUrl` which returns a referral string based on merchant input, with a new `MerchantInput` type. * Introduced an automatic repurchase attempt on the `purchaseGiftCard` mutation. If a gift card purchase using a bank card, bank account, or PayPal fails, the system automatically attempts to repurchase using the Fluz balance. * Introduced the `defaultToBalance` field on `PurchaseGiftCardInput` to set the Fluz balance as the fallback payment method if the primary payment method fails. Defaults to `true`. * Introduced a new access scope, `CREATE_VIRTUALCARD`, to create virtual cards. * Introduced a new function, `createVirtualCard`, to create virtual cards with specific parameters. * Added new query, `getBINs`, to retrieve BINs associated with virtual cards. * Introduced a `merchantCategoryCode` input for cash balance deposits, allowing for enhanced categorization of transactions. * Added a new query, `getMccList`, to retrieve a list of merchant category codes. * Enhanced scope validation for the `getApplicationScopes` query and improved GraphQL type definition management. * Enhanced the handling of stock information for merchant offers: the `Offer` object's `stockInfo` field now returns either `StockInfoFixedType` or `StockInfoVariableType` depending on whether the offer is fixed or variable. * Enhanced error handling with more intuitive error message responses — improved responses for `purchaseGiftCard` no-offer errors and mismatched account and payment methods. **v0.0.21** * Introduced an `idempotencyKey` parameter for `depositCashBalance` and `purchaseGiftCards` to prevent duplicate requests. * Added `purchaseGiftCardInput` types to streamline parameter handling for transactions. * Introduced a new field `purchaseDisplayId` on the `UserPurchase` type, providing a display ID for purchases. * Fixed the `Offer` object's `offeringMerchantId` field to return `offering_merchant_id` instead of `offer_id`. **v0.0.20** * Added `accountId` requirement to `generateUserAccessToken`. * Implemented a new query, `getAccountsByUserId`, to fetch accounts based on `userId`. * Added a new `AccountType` enum to categorize accounts as `CONSUMER` or `BUSINESS`, plus new `Account`, `Business`, and `Seat` objects. **v0.0.17** * Fixed `depositCashBalance` platform channel input so the deposit transaction log source is logged as `API`. ## Change management Additive changes (new fields, new enum values, new operations) are announced at least **7 days** before release when they require action from integrators. Breaking changes are announced at least **14 days** in advance and are typically batched into major release windows. For a complete list of API operations, see the [API reference](/api-reference/overview). # Check Account Balance Source: https://docs.fluz.app/check-account-balance ## Account Balances Your Fluz account can have up to four different balances: 1. **Rewards balance** - A withdrawalable balance that contains your available cashback and bonus rewards. 2. **Cash balance (Spend Account)** - Withdrawable balances that you can use to fund your gift card and virtual card purchases, among other uses. 3. **Fluz prepayment balance** - A non-withdrawable balance you can use towards gift card and virtual card purchases. 4. **Reserve balance** - A balance held in Fluz to cover any transactions that fail to settle, ensuring your account remains in good standing. In order to see your current balances you can use the `getWallet` [query](/api-reference/queries/get-wallet). This can help you determine how much you want to deposit or apply towards a gift card purchase. The sum of these balances makes up your 'available Fluz balance', which determines the total amount you can use towards funding a payment on Fluz. ## User Cash Balance Accounts (Spend Accounts) You can access detailed information about individual cash balance accounts through the `userCashBalances` field. Monitoring account status and creation dates The `userCashBalances` field supports pagination and returns an array of cash balance accounts ordered by creation date (most recent first). ### Sample Response: ```json theme={null} { "data": { "getWallet": { "bankCards": [BankCard], "bankAccounts": [BankAccount], "paypalAccounts": [Paypal], "blockedPaymentTypes": ["BANK_CARD"], "balances": { "rewardsBalance": { "availableBalance": "50.00", "totalBalance": "50.00", "lifetimeBalance": "125.00" }, "cashBalance": { "availableBalance": "100.00", "totalBalance": "100.00", "pendingBalance": "0.00", "lifetimeBalance": "500.00" }, "giftCardCashBalance": { "availableBalance": "25.00", "totalBalance": "25.00", "pendingBalance": "0.00", "lifetimeBalance": "75.00" }, "userCashBalances": [ { "userCashBalanceId": "123e4567-e89b-12d3-a456-426614174000", "totalCashBalance": "100.00", "availableCashBalance": "100.00", "lifetimeCashBalance": "500.00", "nickname": "Primary Account", "status": "ACTIVE", "createdAt": "2024-01-15T10:30:00Z" }, { "userCashBalanceId": "223e4567-e89b-12d3-a456-426614174001", "totalCashBalance": "25.00", "availableCashBalance": "25.00", "lifetimeCashBalance": "75.00", "nickname": "Savings", "status": "ACTIVE", "createdAt": "2024-02-20T14:15:00Z" } ] } } } } ``` ### Query with Pagination: You can paginate through cash balance accounts using the `paginate` parameter: ```graphql theme={null} query { getWallet { balances { userCashBalances(paginate: { limit: 10, offset: 0 }) { userCashBalanceId totalCashBalance availableCashBalance lifetimeCashBalance nickname status createdAt } } } } ``` ### UserCashBalance Fields: | Field | Type | Description | | ---------------------- | --------------------- | ----------------------------------------------------------- | | `userCashBalanceId` | UUID | Unique identifier for the user cash balance account | | `totalCashBalance` | String | Total cash balance in the account | | `availableCashBalance` | String | Available cash balance that can be used for transactions | | `lifetimeCashBalance` | String | Cumulative total ever deposited into this account | | `nickname` | String | Custom nickname for the balance account (optional) | | `status` | UserCashBalanceStatus | Status of the cash balance account (e.g., ACTIVE, INACTIVE) | | `createdAt` | DateTime | Date and time when the account was created |
# Developer Dashboard Source: https://docs.fluz.app/developer-dashboard # Digital Wallet Push Provisioning Source: https://docs.fluz.app/digital-wallet-push-provisioning Use the `getCardProvisioningUrl` query to mint a short, single-use URL that — when opened on your end-user's phone — launches Fluz on the device and adds the virtual card for a given offer to Apple Pay (iOS) or Google Pay (Android). You can deliver the URL however you like: a QR code, an SMS, an email, or an in-app button. Fluz handles the wallet-provisioning flow from there. ## When to use it Anywhere you would normally hand the user a virtual card and want them to be able to tap it in their phone's wallet without typing the card number. Typical flows: * After creating a card offer for the user, present a QR code on screen. * Text or email the URL to the cardholder. * Embed an "Add to Apple Pay / Google Pay" button in your own mobile app. **Prerequisites:** an OAuth access token for the end-user that includes the `CREATE_VIRTUALCARD` scope, and a virtual-card offer for the user (created via the existing offer API). Basic-auth (`client_id` / `client_secret`) is **not** accepted on this query — it must be a user-context bearer token. ## The query ```graphql theme={null} query ProvisionCard($offerId: String!) { getCardProvisioningUrl(input: { offerId: $offerId, platform: IOS }) { url expiresAt } } ``` ### Input | Field | Type | Required | Description | | ---------- | ---------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `offerId` | `String!` (UUIDv4) | yes | The virtual-card offer to provision. Must be active, tokenization-eligible, and accessible to the user's account. | | `platform` | `ProvisioningPlatform` | no | Hint about where the user will open the URL. Defaults to `IOS`. See below — this **does not** lock the URL to one platform. | #### `ProvisioningPlatform` The returned URL is platform-aware: iOS users automatically get the App Clip experience, Android users automatically get the Fluz app deep link. The `platform` value only affects what happens when the URL is opened somewhere **other than** an iOS or Android device (e.g. a desktop browser). | Value | Desktop / unknown-device fallback | | --------------- | --------------------------------- | | `IOS` (default) | Apple App Clip launcher | | `ANDROID` | Fluz Android deep link | | `OTHER` | Fluz web app | Pick the value that best matches where you expect the URL to be opened. If in doubt, leave it as `IOS`. ### Output | Field | Type | Description | | ----------- | --------- | ------------------------------------------------------------------------------------ | | `url` | `String!` | The short URL to deliver to the user. Single-use. | | `expiresAt` | `String!` | ISO-8601 timestamp. The URL stops working at this time (≈ 5 minutes after creation). | ## Example ### Request ```bash theme={null} curl -X POST https://transactional-graph.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "query($id:String!){ getCardProvisioningUrl(input:{ offerId:$id, platform:IOS }){ url expiresAt } }", "variables": { "id": "11111111-2222-3333-4444-555555555555" } }' ``` ### Response ```json theme={null} { "data": { "getCardProvisioningUrl": { "url": "https://fluz.app.link/abc123XYZ", "expiresAt": "2026-05-26T17:42:11.000Z" } } } ``` ## How to deliver the URL The URL is opaque and contains no sensitive data — it's safe to surface in QR codes, SMS, email, or in-app UI. Common patterns: * **QR code on a screen** — generate a QR from `url` and show it; the user scans with their phone camera. * **SMS / email** — send the link directly to the user's phone or inbox. * **In-app deep link** — wire a button in your mobile app that opens `url`. Whatever the delivery channel, the user must open the URL on a mobile device — that's where the wallet provisioning happens. ## Lifetime & re-issuing * Each call returns a **fresh, single-use** URL. * The URL is valid until `expiresAt` (≈ 5 minutes). * If a user doesn't act in time, simply call `getCardProvisioningUrl` again to mint a new one. There is no separate "refresh" endpoint. ## Error handling All errors come back in the standard Fluz GraphQL error shape, with the error name in `extensions.code`. | Code | What it means | What to do | | --------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------- | | `Arguments.INVALID` | `offerId` isn't a valid UUID. | Validate input on your side. | | `VirtualCard.OFFER_NOT_FOUND` | Offer doesn't exist or is inactive. | Confirm the offer id; create a new offer if needed. | | `VirtualCard.OFFER_NOT_ACCESSIBLE` | The user's account isn't eligible for this offer (campaign access). | Surface a generic "not available" message. | | `VirtualCard.OFFER_NOT_AVAILABLE` | The offer has already been redeemed into a card that is no longer active. | Don't retry with the same offer; create a new one. | | `VirtualCard.OFFER_NOT_TOKENIZATION_ELIGIBLE` | The offer's card program doesn't support wallet provisioning. | Don't offer "Add to wallet" for this card program. | | `Auth.USER_REVOKED_DEVELOPER_ACCESS` | The user has revoked OAuth access to your app. | Send the user back through OAuth consent. | | `Auth.INVALID_SCOPE` | The token doesn't have `CREATE_VIRTUALCARD`. | Request the scope during OAuth. | | `Generic.EXTERNAL_SERVICE_ERROR` | Transient downstream issue. | Retry — the call is safe to repeat. | ## FAQ No. The URL carries a short-lived opaque lookup id only. Credentials are exchanged securely on the device once the user opens the link. No. It's single-use. Generate a new one each time you need to surface the flow. They'll land on the fallback you selected with `platform` (App Clip launcher, Android deep link, or Fluz web app). For the wallet provisioning itself to work, they need to end up opening the URL on a mobile device. No. The same URL works for both — Fluz routes per-device automatically. ## Next steps Distribute virtual cards to recipients by email, SMS, or share link. Set a PIN on eligible cards before in-person, PIN-prompted transactions. # Go to Fluz App Source: https://docs.fluz.app/explore-fluz-app # API Features Source: https://docs.fluz.app/features Everything the Fluz API can do — every capability is account-agnostic. Link bank cards and bank accounts via Plaid, then pull funds on demand. Spend accounts, deposits, withdrawals, and internal or cross-account transfers. Spend controls, lock/unlock, PINs, wallet provisioning, and bulk issuance up to 10,000. Thousands of brands, offer comparison, stock checks, and discount pricing. Generate hosted card links that recipients claim, with full link lifecycle control. Look up recipients by phone or email and transfer funds to other Fluz wallets. Query all activity and annotate with memos, categories, and attachments. Request manager approval for cards, transfers, purchases, and limit changes. Add team members, issue them cards, and route approvals. ## Go deeper Every query, mutation, and type in the GraphQL schema. Copy-paste scripts for single operations — tokens, cards, money movement. Recent updates, additions, and breaking changes. **Building for customers instead?** Every capability on this page works identically on connected accounts — send a customer-scoped OAuth token instead of your own. See [Build a platform](/build-a-platform). # Overview Source: https://docs.fluz.app/features/account-to-account-transfers **Account to account transfers** move funds between two Fluz accounts using the `createTransfer` mutation. Unlike an internal transfer — which moves money between two of *your own* spend accounts — an account to account transfer sends money to a **different Fluz account**: one of your users, your application, or between two of your users. Every transfer is a two-step flow: first identify the recipient, then create the transfer. | Step | Page | Purpose | | --------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | 1. Find the recipient | [Wallet Transfer Recipient Lookup](/features/recipient-lookup) | Resolve a phone, email, or company name into the recipient's account ID. | | 2. Send the funds | [Transfer to Another Fluz Account](/features/transfer-to-another-fluz-wallet) | Move the funds to the recipient with `createTransfer`. | **Not the same as an internal transfer.** To move money between your own spend accounts, use [Transfer Funds Internally](/features/transfer-between-spend-accounts) instead. Account to account transfers are for moving funds to a **separate** Fluz account. *** ## The Three Transfer Directions The same `createTransfer` mutation supports three directions. Which one you're performing is determined by your authentication method and whether you provide a `destination`. | Direction | Auth method | Sender | Typical use | | ---------------------- | ------------ | ---------------- | ---------------------------------------------------------------------------------- | | **User → Application** | Bearer token | The user | Collecting a payment from a user (no `destination` needed — defaults to your app). | | **Application → User** | Basic Auth | Your application | Disbursing funds to a user (`destination` required). | | **User → User** | Bearer token | The user | Moving funds between two of your users (`destination` set to the recipient). | **Account to account transfers require a public application.** Basic Auth (application-as-sender) transfers require your application to be in `ACTIVE` status. Personal applications cannot send via Basic Auth. The recipient must also be a registered user of your application. *** ## Step 1 — Look Up the Recipient Before sending, resolve the recipient into an `accountId` using the lookup queries (both require the `QUERY_RECIPIENT` scope): * `lookupUser` — find an individual by **phone number** (E.164 format) or **email**. * `lookupBusiness` — find a business by **company name** (case-insensitive exact match; searches both the registered name and DBA). If you already store the recipient's Fluz `accountId` — or assigned them an `externalReferenceId` when you created them — you can skip the lookup and pass that identifier directly in Step 2. See [Wallet Transfer Recipient Lookup](/features/recipient-lookup) for inputs, responses, and error codes. *** ## Step 2 — Create the Transfer Send the funds with the `createTransfer` mutation. At a minimum you provide an `idempotencyKey` and an `amount`; the `destination` and funding source depend on the direction. * **Identify the recipient** by `accountId` or by the `externalReferenceId` you assigned — provide one, not both. Optionally target a specific `userCashBalanceId` on the recipient's account. * **Fund the transfer** from the sender's cash balance by default, or (Bearer token only) from a linked `bankCardId`, `bankAccountId`, or `paypalVaultId`. Only one funding source per request. * **Annotate** the transfer with an optional `memo`, `transactionCategory`, or `attachmentId`. Bearer-token (user-sent) transfers require the `MAKE_PAYOUT_TRANSFER_SEND` scope. See [Transfer to Another Fluz Account](/features/transfer-to-another-fluz-wallet) for the full input reference, examples for each direction, and error handling. *** ## Idempotency Every transfer must include a unique, client-generated `idempotencyKey`. If the same key is submitted more than once, the API returns the result of the original request instead of creating a duplicate transfer. Keys are valid for 10 minutes. *** ## Requirements at a Glance | Operation | Auth | Scope | | ------------------------------------ | -------------------- | ----------------------------- | | Look up a recipient | Bearer token | `QUERY_RECIPIENT` | | Create a transfer (user-sent) | Bearer token | `MAKE_PAYOUT_TRANSFER_SEND` | | Create a transfer (application-sent) | Basic Auth (Api Key) | Public / `ACTIVE` application | *** **Want to learn more?** Speak with our experts for more info or to request a demo. # Add Bank Card Source: https://docs.fluz.app/features/add-bank-card # Background A funding source, otherwise known as payment method, is used on Fluz to purchase gift cards, fund virtual card transactions, deposit funds to your Fluz balance, and more. The first thing you'll want to do is make sure you have at least one funding source to complete transactions on Fluz. A user account can have the following types: * **Bank Card** - A debit, credit or prepaid card. Please note, credit cards can carry higher fees and lower cashback rates than debit cards. * **Bank Account** - Bank accounts have no fees. However, they can only be added in Fluz app or web portal. * **PayPal Account** - Digital wallets like PayPal often carry the highest fees and need to be added from the Fluz app or web portal. You must add a bank card as a backup payment method to be able to make a purchase. **Currently, users can only add bank cards as a funding source via the API.** # Add Bank Card ## Sample request You can add a new bank card with the `addBankCard` [mutation](/api-reference/overview). This mutation allows you to provide the necessary details for adding a bank card to a user's account. The query takes an `AddBankCardInput` input object. ```json theme={null} { "query": "mutation addBankCard($input: AddBankCardInput!) { addBankCard(input: $input) { bankCardId ownerAccountId addedUserId cardType cardProcessor cardholderName lastFourDigits expirationMonth expirationYear cardStatus billingAddressId nickname }}", "variables": { "input": { "cardNumber": "4111111111111111", "expirationMonth": "01", "expirationYear": "2029", "cvv": "111", "cardholderName": "John Doe", "nickname": "My travel card", "preferredMerchantCategoryCode": "5411", "billingAddress": { "streetAddressLine1": "123 Example St", "streetAddressLine2": "Unit 1", "country": "United States", "city": "New York City", "state": "New York", "postalCode": "12345" } } } } ``` This mutation requires the `AddBankCardInput` input type. Any field marked with an exclamation mark (`!`) in the schema is mandatory and must be included in the request. To associate an address with your bank card, you must use either the `billingAddress` or `userAddressId` input field. Ensure that only one of these fields is provided to meet validation requirements. * **New Address**: To add a new address, use the `billingAddress` input field to define the address details associated with this card. * **Existing Address**: To link an existing address, use the `userAddressId` input field, specifying the identifier for the address already stored in the system. You can use the `getUserAddresses` query to look up an existing `userAddressId`. **Billing address formatting** Provide the billing address exactly as it appears on the card issuer's records, using the structured fields (`streetAddressLine1`, `streetAddressLine2`, `city`, `state`, `postalCode`, `country`). A mismatched or malformed billing address can cause the card to fail the address verification (AVS) check. International billing addresses are accepted for bank cards. See [Address Formatting Requirements](/concepts/address-formatting-requirements) for formatting rules and examples. | Field name | Type | Description | | ----------------------------- | ---------------- | ------------------------------------------------------------------------------- | | cardNumber | String! | The card number. This field requires the full card number, typically 16 digits. | | expirationMonth | String! | The expiration month of the card, formatted as MM (e.g., "07" for July). | | expirationYear | String! | The three- or four-digit security code, found on the back or front of the card. | | cvv | String! | The three- or four-digit security code, found on the back or front of the card. | | cardholderName | String! | The name of the cardholder as it appears on the card. | | billingAddress | UserAddressInput | Optional. Use this field to create the card with a new billing address. | | userAddressId | UUID | Optional. Use this field to associate the card with an existing user address. | | nickname | String | Optional. Use this field to create a card with a nickname. | | preferredMerchantCategoryCode | String | Optional. Use this field to associate the card with a merchant category code. | ## AddBankCardInput ```json theme={null} { "cardNumber": "xyz789", "expirationMonth": "xyz789", "expirationYear": "abc123", "cvv": "abc123", "cardholderName": "abc123", "billingAddress": UserAddressInput, "userAddressId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "nickname": "My travel card", "preferredMerchantCategoryCode": "5411" } ``` ## Sample response The response from the `addBankCard` mutation will include the fields that you specified in the mutation request, along with the relevant ID values and card processor. ```json theme={null} { "data": { "addBankCard": { "bankCardId": "4111111111111111", "ownerAccountId": "a578fe07-7165-47b8-b147-2251c99b7fc1", "addedUserId": "8d197a56-53df-439b-85cd-bb88dfca9a5f", "cardType": "Visa", "cardProcessor": "Stripe", "cardholderName": "John Doe", "lastFourDigits": "1234", "expirationMonth": "12", "expirationYear": "2025", "cardStatus": "active", "billingAddressId": "e7979eb7-1727-48ed-8c5a-c56532908c1e", "nickname": "My travel card" } } } ``` **Only available to personal applications** Currently, only personal applications will be granted the scope to add bank cards to an account. Publicly available applications looking to add bank cards to an account will need to complete PCI certification and an attestation of PCI compliance. If you would like to set a given funding source as the primary, preferred or backup on your account, this will need to be managed in the Fluz dashboard. **Want to learn more?** Contact us at your support email. Speak with our experts for more info or to request a demo.
# Add Virtual Card Address Source: https://docs.fluz.app/features/add-billing-address Save a billing address for virtual card issuance on the caller's account. The address is saved as a `BILLING` user address and can later be passed to `createVirtualCard` as `userAddressId`. When `authUserId` is provided, the address is saved for that authorized user's underlying user while remaining attached to the caller's account. The `authUserId` must be an ACTIVE non-owner authorized user assignment on the caller's account. If an identical billing address already exists for the target cardholder on the caller's account, the existing address is returned instead of creating a duplicate. Matching is done on address fields, account, target user, and `BILLING` type. **Prerequisites:** a Bearer token with the `CREATE_VIRTUALCARD` scope. **The billing address must be verifiable** This mutation stores the billing address. It must be a real, deliverable **US** address (`US`, `USA`, or `United States`) with a correct city, state, and ZIP, and **PO boxes are not accepted**. The USPS/Smarty check runs when a virtual card is created — an address that can't be verified there causes card creation to fail with `VC-0025`. See [Address Formatting Requirements](/concepts/address-formatting-requirements) for the full field rules and examples. ```graphql theme={null} mutation AddVirtualCardAddress($input: AddVirtualCardAddressInput!) { addVirtualCardAddress(input: $input) { userAddressId streetAddressLine1 streetAddressLine2 country city state postalCode } } ``` ## Parameters | Parameter | Type | Required | Description | | --------------------------------------- | ------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | | input | AddVirtualCardAddressInput! | Yes | Wrapper object for the billing address request. | | input.billingAddress | VirtualCardBillingAddressInput! | Yes | Billing address to save for virtual card issuance. | | input.billingAddress.streetAddressLine1 | String! | Yes | Street address. PO boxes are not accepted by the issuer. | | input.billingAddress.streetAddressLine2 | String | No | Optional apartment, suite, or secondary address line. Empty values are saved as `null`; non-empty values are trimmed. | | input.billingAddress.country | String! | Yes | Country name. Currently only United States addresses are supported by the card issuer. | | input.billingAddress.city | String! | Yes | City. Saved in uppercase. | | input.billingAddress.state | String! | Yes | State name or two-letter US state code. Saved in uppercase. | | input.billingAddress.postalCode | String! | Yes | 5-digit US ZIP code. Saved in uppercase. | | input.authUserId | UUID | No | Authorized user ID (UAC role assignment ID). When provided, the address is saved for that authorized cardholder. | ## Response ```json theme={null} { "data": { "addVirtualCardAddress": { "userAddressId": "1f6b2c3d-4e5f-4a67-9a10-2b3c4d5e6f70", "streetAddressLine1": "123 Main St", "streetAddressLine2": "Apt 5", "country": "United States", "city": "NEW YORK", "state": "NY", "postalCode": "10001" } } } ``` ## Response fields | Field | Type | Description | | -------------------- | ------ | ------------------------------------------------------------------------------------ | | `userAddressId` | UUID | Saved billing address ID. Pass this value as `userAddressId` to `createVirtualCard`. | | `streetAddressLine1` | String | Street address line 1. | | `streetAddressLine2` | String | Street address line 2, or `null` when not provided. | | `country` | String | Country name. | | `city` | String | City saved on the address. | | `state` | String | State saved on the address. | | `postalCode` | String | Postal code saved on the address. | ## Example requests Save a billing address for the caller: ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation AddVirtualCardAddress($input: AddVirtualCardAddressInput!) { addVirtualCardAddress(input: $input) { userAddressId streetAddressLine1 streetAddressLine2 country city state postalCode } }", "variables": { "input": { "billingAddress": { "streetAddressLine1": "123 Main St", "streetAddressLine2": "Apt 5", "country": "United States", "city": "New York", "state": "NY", "postalCode": "10001" } } } }' ``` Save a billing address for an authorized user: ```bash cURL theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation AddVirtualCardAddress($input: AddVirtualCardAddressInput!) { addVirtualCardAddress(input: $input) { userAddressId streetAddressLine1 streetAddressLine2 country city state postalCode } }", "variables": { "input": { "authUserId": "8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d", "billingAddress": { "streetAddressLine1": "456 Market St", "streetAddressLine2": "Suite 200", "country": "United States", "city": "San Francisco", "state": "CA", "postalCode": "94105" } } } }' ``` ```typescript TypeScript theme={null} const response = await fetch('https://transactional-graph.staging.fluzapp.com/api/v1/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify({ query: ` mutation AddVirtualCardAddress( $input: AddVirtualCardAddressInput! ) { addVirtualCardAddress(input: $input) { userAddressId streetAddressLine1 streetAddressLine2 country city state postalCode } } `, variables: { input: { authUserId: '8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d', billingAddress: { streetAddressLine1: '456 Market St', streetAddressLine2: 'Suite 200', country: 'United States', city: 'San Francisco', state: 'CA', postalCode: '94105', }, }, }, }), }); const data = await response.json(); console.log('Virtual card address saved:', data.data.addVirtualCardAddress); ``` ## Error codes | Code | Message | Description | | ----------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | `ARG-0001` | Invalid arguments received | A required address field is missing, a value is empty, or `authUserId` is not a valid UUID. | | `AUTH-0008` | Invalid user access | The Bearer token could not be resolved to a caller. Verify the token is valid. | | `AUTH-0031` | The requested scopes must be granted by the user first. | The token is missing the `CREATE_VIRTUALCARD` scope required to save virtual card addresses. | | `AUTH-0034` | No authorized user found with this id on the caller's account. | The `authUserId` does not exist on the caller's account, is not ACTIVE, or refers to an OWNER assignment. | | `VC-0001` | Please try another payment method. If you continue experiencing issues, please contact our support team. | A general failure occurred while saving the billing address. Please retry or contact support. | ## Next steps Pass the returned `userAddressId` into `createVirtualCard`. Required before the card can be used for in-person or PIN-prompted transactions. # Add Expense Details Source: https://docs.fluz.app/features/add-expense-details You can enrich any transaction with a **memo**, a **category**, and/or an **attachment** (e.g. receipts, invoices, purchase orders). Annotations can be added at the time of the original transaction or updated afterward using the `updateTransactionMetadata` mutation. For accounts with ERP integrations enabled and an active QuickBooks Online connection, you can also manage ERP transaction metadata: accounting category, vendor, customer, billable status, and an ERP-specific memo. ERP metadata is separate from annotations. Annotations are supported on: * Deposits (`depositCashBalance`) * Gift card purchases (`purchaseGiftCard`) * Wallet transfers (`createTransfer`, `transferInternalBalance`) * Virtual card creation (`createVirtualCard`) * `attachment` is excluded ERP metadata is supported on: * Existing transactions (`updateTransactionMetadata.input.erpTransactionMetadata`) * Bulk transaction updates (`bulkUpdateErpTransactionMetadata`) * Transaction reads (`Transaction.erpMetadata`, `erpTransactionMetadata`, `erpTransactionMetadataList`) *** ## How it works 1. **Optional:** If you want to attach a file, upload it first via the REST upload endpoint. You'll get back an `attachmentId`. 2. Pass `memo`, `transactionCategory`, and/or `attachmentId` into your mutation input — either at transaction time or later via `updateTransactionMetadata`. 3. For ERP metadata, first query imported QuickBooks Online reference items, or pass names that should be resolved or created in QuickBooks Online. 4. Update ERP metadata on one transaction with `updateTransactionMetadata.input.erpTransactionMetadata`, or update up to 100 transactions with `bulkUpdateErpTransactionMetadata`. 5. Read annotations back on `getTransactions`, `getUserPurchases`, or the mutation response. Read ERP details on `Transaction.erpMetadata`, `erpTransactionMetadata`, or `erpTransactionMetadataList`. The `attachmentUrl` field returns a short-lived signed URL for file access. *** ## Step 1: Upload an attachment (optional) > This is a REST endpoint, not a GraphQL mutation ### Endpoint `POST /api/v1/file-upload/transaction-memo-attachment` ### Authentication `Authorization: Bearer ` ### Request Send the file as `multipart/form-data` with the field name `file`. **Accepted types:** `application/pdf`, `image/png` > ⚠️ JPEG is not accepted for transaction attachments. ### Sample Request ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/file-upload/transaction-memo-attachment \ -H "Authorization: Bearer " \ -F "file=@receipt.pdf" ``` ### Response ```json theme={null} { "attachmentId": "3f2a1b4c-e5d6-7890-abcd-ef1234567890" } ``` Copy the `attachmentId` — you'll pass it into your mutation input in the next step. > The `attachmentId` is scoped to your account. The system verifies the file exists in your account's storage when you submit the mutation. An ID from a different account will be rejected. ### Upload Errors | Cause | Status | Details | | --------------------------------- | ------ | ------------------- | | No file included in request | 400 | Missing file | | File type not allowed (e.g. JPEG) | 400 | `INVALID_ARGUMENTS` | *** ## Step 2: Annotate the transaction You can provide annotations at the time of the original transaction **or** update them afterward. ### Option A — At transaction time The following mutations accept `memo`, `transactionCategory`, and `attachmentId` as optional fields in their input: * `depositCashBalance` → `DepositCashBalanceInput` * `purchaseGiftCard` → `PurchaseGiftCardInput` * `createTransfer` → `CreateTransferInput` * `transferInternalBalance` → `TransferInternalBalanceInput` #### Annotation Fields | Field | Type | Description | | --------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- | | `memo` | String | Free-text note. Max 255 characters. | | `transactionCategory` | String | Category label. Free-form — categories are created automatically on first use and reused if the same name is passed again. | | `attachmentId` | String | The ID returned by the upload endpoint. The file must be uploaded before submitting the mutation. | #### Sample — Purchase Gift Card with Annotation ```graphql theme={null} mutation purchaseGiftCard($input: PurchaseGiftCardInput!) { purchaseGiftCard(input: $input) { purchaseDisplayId purchaseAmount memo transactionCategory attachmentUrl giftCard { giftCardId status } } } ``` ```json theme={null} { "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "offerId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "amount": 50.0, "balanceAmount": 50.0, "memo": "Team lunch — Q2", "transactionCategory": "Meals & Entertainment", "attachmentId": "3f2a1b4c-e5d6-7890-abcd-ef1234567890" } ``` *** ### Option B — After the transaction (`updateTransactionMetadata`) Use this mutation to add or update annotations on any existing transaction. > **Partial update semantics:** Only the fields you include are updated. Omitted fields are left unchanged. Pass `null` to clear a field. #### Mutation ```graphql theme={null} mutation updateTransactionMetadata($input: UpdateTransactionMetadataInput!) { updateTransactionMetadata(input: $input) { recordId memo transactionCategory attachmentUrl } } ``` #### UpdateTransactionMetadataInput | Field | Type | Required | Description | | ------------------------ | ----------------------------------- | -------- | ----------------------------------------------------------------------------------------- | | `recordId` | UUID! | Yes | The `recordId` of the transaction to update. | | `memo` | String | No | Free-text note. Max 255 characters. Omit to leave unchanged; pass `null` to clear. | | `transactionCategory` | String | No | Category label. Omit to leave unchanged; pass `null` to clear. | | `attachmentId` | String | No | ID from the upload endpoint. Omit to leave unchanged; pass `null` to clear. | | `erpTransactionMetadata` | `UpdateErpTransactionMetadataInput` | No | Optional ERP-aware categorization. Omit, or pass `null`, to leave ERP metadata unchanged. | #### Required scopes `LIST_PAYMENT` and `LIST_PURCHASES`. If you include `erpTransactionMetadata`, the request also requires `MANAGE_ERP_TRANSACTION_METADATA`. #### Sample — Add a memo and category ```json theme={null} { "recordId": "550e8400-e29b-41d4-a716-446655440000", "memo": "Q1 vendor payment", "transactionCategory": "Operating Expenses" } ``` #### Sample — Attach a file to an existing transaction ```json theme={null} { "recordId": "550e8400-e29b-41d4-a716-446655440000", "attachmentId": "3f2a1b4c-e5d6-7890-abcd-ef1234567890" } ``` #### Sample — Clear a memo ```json theme={null} { "recordId": "550e8400-e29b-41d4-a716-446655440000", "memo": null } ``` #### Sample Response ```json theme={null} { "data": { "updateTransactionMetadata": { "recordId": "550e8400-e29b-41d4-a716-446655440000", "memo": "Q1 vendor payment", "transactionCategory": "Operating Expenses", "attachmentUrl": "https://storage.googleapis.com/..." } } } ``` #### Errors | Cause | Error | | ---------------------------------------------------------- | ------------------------------------------------------------------------------ | | `recordId` not found or belongs to a different account | `INVALID_ARGUMENTS` — transaction not found | | Transaction type does not support metadata | `INVALID_ARGUMENTS` — transaction does not support metadata | | `attachmentId` is not a valid UUID | `INVALID_ARGUMENTS` — Invalid attachment ID | | File not found in storage (not uploaded, or wrong account) | `INVALID_ARGUMENTS` — Attachment file not found. Please upload the file first. | *** ## Step 3: Manage ERP metadata ERP metadata is used to categorize transactions before exporting them to the connected accounting provider. The currently available provider is QuickBooks Online. ERP metadata fields are separate from annotations: * Annotation `memo` is limited to 255 characters. * ERP `erpTransactionMetadata.memo` is limited to 4000 characters. * Annotation `transactionCategory` is a Fluz category label. * ERP `categoryReferenceItemId` points to a QuickBooks Online chart-of-accounts item. ### UpdateErpTransactionMetadataInput | Field | Type | Description | | ------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `categoryReferenceItemId` | `UUID` | QuickBooks Online chart-of-accounts reference item ID. Omit to leave unchanged; pass `null` to clear. Must refer to an active chart-of-accounts item. | | `categoryName` | `String` | Find or create a QuickBooks Online Expense account by name, then use it as the category. Ignored when `categoryReferenceItemId` is supplied. Max 100 characters. | | `vendorReferenceItemId` | `UUID` | QuickBooks Online vendor reference item ID. Omit to leave unchanged; pass `null` to clear. Must refer to an active vendor. | | `vendorName` | `String` | Find or create a QuickBooks Online vendor by name. Ignored when `vendorReferenceItemId` is supplied. Max 100 characters. | | `customerReferenceItemId` | `UUID` | QuickBooks Online customer reference item ID. Omit to leave unchanged; pass `null` to clear. Must refer to an active customer. | | `customerName` | `String` | Find or create a QuickBooks Online customer by name. Ignored when `customerReferenceItemId` is supplied. Max 100 characters. | | `isBillable` | `Boolean` | Sets billable status. Omit to leave unchanged; pass `null` to clear. If `true`, a customer is required before the transaction can become `READY`. | | `memo` | `String` | ERP memo. Omit to leave unchanged; pass `null` to clear. Max 4000 characters. | Leave `erpTransactionMetadata` out of the request when you do not want to update ERP metadata. ### Sample: Update annotations and ERP metadata together ```graphql theme={null} mutation updateTransactionMetadata($input: UpdateTransactionMetadataInput!) { updateTransactionMetadata(input: $input) { recordId memo transactionCategory erpMetadata { category { referenceItemId name } vendor { referenceItemId name } customer { referenceItemId name } isBillable memo syncStatus } } } ``` ```json theme={null} { "input": { "recordId": "550e8400-e29b-41d4-a716-446655440000", "memo": "Receipt added in Fluz", "transactionCategory": "Office Supplies", "erpTransactionMetadata": { "categoryReferenceItemId": "11111111-1111-4111-8111-111111111111", "vendorName": "Example Vendor", "customerReferenceItemId": "22222222-2222-4222-8222-222222222222", "isBillable": true, "memo": "QuickBooks Online memo for client office supplies" } } } ``` *** ## Step 4: Find ERP reference items Use these queries to find imported QuickBooks Online reference items before setting `categoryReferenceItemId`, `vendorReferenceItemId`, or `customerReferenceItemId`. | Query | Returns | | -------------------- | --------------------------------------------------- | | `erpChartOfAccounts` | Imported QuickBooks Online chart-of-accounts items. | | `erpVendors` | Imported QuickBooks Online vendors. | | `erpCustomers` | Imported QuickBooks Online customers. | ### Reference item fields | Field | Description | | -------------------- | ------------------------------------------------------------------------------------- | | `referenceItemId` | Fluz ID for this imported ERP reference item. Use this in ERP metadata update inputs. | | `externalId` | QuickBooks Online ID. | | `name` | Display name. | | `fullyQualifiedName` | Fully qualified provider name, when available. | | `accountType` | Chart-of-accounts account type. Only populated for chart-of-accounts items. | | `accountSubType` | Chart-of-accounts subtype. Only populated for chart-of-accounts items. | | `active` | Whether the imported provider item is active. | | `lastChangedAt` | Last QuickBooks Online update timestamp, when available. | ### Reference item filters | Field | Type | Description | | ---------------- | ------------------------- | ------------------------------------------------------------------------------------- | | `q` | `String` | Case-insensitive substring search on name. | | `active` | `Boolean` | Defaults to `true`. Set `false` to list inactive items. | | `accountType` | `[String!]` | Chart-of-accounts only. Filters by QuickBooks Online account type, such as `Expense`. | | `accountSubType` | `[String!]` | Chart-of-accounts only. Filters by QuickBooks Online account subtype. | | `sortKey` | `ErpReferenceItemSortKey` | `NAME`, `FULLY_QUALIFIED_NAME`, or `LAST_CHANGED_AT`. Defaults to `NAME`. | | `sortOrder` | `SortOrder` | `ASC` or `DESC`. Defaults to `ASC`. | ### Sample: Search chart of accounts ```graphql theme={null} query erpChartOfAccounts { erpChartOfAccounts( filter: { q: "office" accountType: ["Expense"] sortKey: NAME sortOrder: ASC } paginate: { limit: 10, offset: 0 } ) { totalCount hasNextPage items { referenceItemId externalId name fullyQualifiedName accountType accountSubType active lastChangedAt } } } ``` ### Sample: Search vendors and customers ```graphql theme={null} query erpCounterparties { erpVendors( filter: { q: "example vendor" } paginate: { limit: 10, offset: 0 } ) { items { referenceItemId name active } } erpCustomers( filter: { q: "example company" } paginate: { limit: 10, offset: 0 } ) { items { referenceItemId name active } } } ``` *** ## Step 5: Read ERP metadata ERP metadata can be read from the transaction object or through dedicated ERP metadata queries. ### Read ERP metadata in getTransactions ```graphql theme={null} query getTransactionsWithErpMetadata { getTransactions(paginate: { limit: 10, offset: 0 }) { totalCount hasNextPage transactions { recordId amount memo transactionCategory erpMetadata { transactionMetadataId transactionRecordId category { referenceItemId name } vendor { referenceItemId name } customer { referenceItemId name } isBillable memo syncStatus isSyncing updatedAt } } } } ``` `erpMetadata` is `null` when the account has no active ERP connection or the transaction has no ERP metadata. ### Read one transaction's ERP metadata ```graphql theme={null} query erpTransactionMetadata($transactionRecordId: UUID!) { erpTransactionMetadata(transactionRecordId: $transactionRecordId) { transactionMetadataId transactionRecordId category { referenceItemId name } vendor { referenceItemId name } customer { referenceItemId name } isBillable memo syncStatus isSyncing updatedAt } } ``` ```json theme={null} { "transactionRecordId": "550e8400-e29b-41d4-a716-446655440000" } ``` This query returns `null`, not an error, when there is no ERP metadata for the transaction or the account has no active ERP connection. ### List ERP metadata records ```graphql theme={null} query erpTransactionMetadataList { erpTransactionMetadataList( filter: { syncStatus: DRAFT, updatedAfter: "2026-07-01T00:00:00.000Z" } paginate: { limit: 20, offset: 0 } ) { totalCount hasNextPage items { transactionRecordId category { referenceItemId name } isBillable memo syncStatus updatedAt } } } ``` ### ERP metadata list filters | Field | Type | Description | | ---------------------- | ------------------------------- | ------------------------------------------------- | | `syncStatus` | `ErpTransactionSyncStatus` | Filter by sync status. | | `updatedAfter` | `DateTime` | Return metadata updated after this timestamp. | | `transactionRecordIds` | `[UUID!]` | Limit results to specific transaction record IDs. | | `sortKey` | `ErpTransactionMetadataSortKey` | Currently `UPDATED_AT`. Defaults to `UPDATED_AT`. | | `sortOrder` | `SortOrder` | `ASC` or `DESC`. Defaults to `DESC`. | *** ## ERP sync status | Status | Meaning | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DRAFT` | Missing required ERP fields and cannot be exported yet. Most transaction types need a category. If `isBillable` is `true`, a customer is also required. | | `READY` | Required ERP fields are present and the transaction is ready to export. | | `SYNC_STARTED` | Export to the accounting provider is in progress. The transaction cannot be edited while this status is active. | | `SYNCED` | Export succeeded. The transaction cannot be modified through ERP metadata APIs after this point. | | `SYNC_FAILED` | The last export attempt failed. Edit the metadata and retry export. | *** ## Bulk update ERP metadata Use `bulkUpdateErpTransactionMetadata` to update ERP metadata for up to 100 transactions in one request. Each item uses the same `UpdateErpTransactionMetadataInput` fields. Omitted fields are left unchanged; nullable reference fields, `memo`, and `isBillable` can be passed as `null` to clear them. ### Mutation ```graphql theme={null} mutation bulkUpdateErpTransactionMetadata( $items: [BulkUpdateErpTransactionMetadataItem!]! ) { bulkUpdateErpTransactionMetadata(items: $items) { succeeded failed { transactionRecordId error { code message } } } } ``` ### Variables ```json theme={null} { "items": [ { "transactionRecordId": "550e8400-e29b-41d4-a716-446655440000", "erpTransactionMetadata": { "categoryName": "Office Supplies", "vendorName": "Example Vendor", "memo": "Bulk-updated QuickBooks Online memo" } }, { "transactionRecordId": "660e8400-e29b-41d4-a716-446655440000", "erpTransactionMetadata": { "categoryReferenceItemId": "11111111-1111-4111-8111-111111111111", "customerName": "Example Company LLC", "isBillable": true } } ] } ``` ### Sample response ```json theme={null} { "data": { "bulkUpdateErpTransactionMetadata": { "succeeded": ["550e8400-e29b-41d4-a716-446655440000"], "failed": [ { "transactionRecordId": "660e8400-e29b-41d4-a716-446655440000", "error": { "code": "ERP-0019", "message": "The reference item type does not match the expected type for this field." } } ] } } } ``` Per-item ERP validation failures are returned in `failed`, other valid items can still succeed. *** ## Reading annotations Annotations are returned on the following: | Query / Mutation | Type | Fields | | ---------------------------- | ---------------------------------- | ---------------------------------------------------------------------- | | `getTransactions` | `Transaction` | `memo`, `transactionCategory`, `attachmentUrl`, optional `erpMetadata` | | `getUserPurchases` | `UserPurchase` | `memo`, `transactionCategory`, `attachmentUrl` | | `updateTransactionMetadata` | `Transaction` | `memo`, `transactionCategory`, `attachmentUrl`, optional `erpMetadata` | | `depositCashBalance` | `CashBalanceDeposit` | `attachmentUrl` | | `erpTransactionMetadata` | `ErpTransactionMetadata` | ERP category, vendor, customer, billable status, ERP memo, sync status | | `erpTransactionMetadataList` | `ErpTransactionMetadataConnection` | Paginated ERP metadata records | > ⚠️ **`attachmentUrl` is a signed URL.** It expires shortly after being generated. Do not store it — re-fetch the transaction when you need to display or access the file. *** ## Common errors | Cause | Error | | ---------------------------------------------------------- | ----------------------------------------- | | Missing annotation scopes | `AUTH-0031` / invalid scope | | Missing `VIEW_ERP_TRANSACTION_METADATA` for ERP reads | `AUTH-0031` / invalid scope | | Missing `MANAGE_ERP_TRANSACTION_METADATA` for ERP writes | `AUTH-0031` / invalid scope | | ERP integrations are not enabled for the account | `ERP-0018` / forbidden | | No active QuickBooks Online connection | `ERP-0001` / no active connection | | QuickBooks Online connection requires attention | `ERP-0016` / connection action required | | Transaction not found for this account | `ERP-0026` / transaction not found | | Transaction already synced | `ERP-0027` / transaction already synced | | Transaction export is in progress | `ERP-0052` / transaction sync in progress | | Reference item is the wrong type or not in this connection | `ERP-0019` / invalid reference type | | Reference item is inactive | `ERP-0028` / stale reference item | | ERP memo exceeds 4000 characters | `ERP-0029` / memo too long | *** ## Notes and limits * `recordId` is the transaction record ID returned by transaction queries. * Annotation updates are partial: omitted fields stay unchanged, and `null` clears. * ERP metadata updates are also partial: omitted fields stay unchanged, and supported nullable fields can be cleared with `null`. * In `updateTransactionMetadata`, `erpTransactionMetadata: null` is a no-op, not a clear. * In `updateTransactionMetadata`, `erpTransactionMetadata: {}` is invalid. Omit the field instead. * If both an ERP reference ID and a name are provided for the same field, the reference ID wins. * `categoryName` creates or selects a QuickBooks Online account with account type `Expense`. * `vendorName` creates or selects a QuickBooks Online vendor. * `customerName` creates or selects a QuickBooks Online customer. * GraphQL pagination uses `OffsetInput`, `limit` defaults to 20 and is capped by the API pagination cap. # Approvals & Requests Overview Source: https://docs.fluz.app/features/approvals-and-requests Use the Fluz API to submit approval requests for sensitive account actions, list pending requests, and approve or decline them on behalf of managers. Approval requests are available through the Transactional Graph Service GraphQL API. When a request is submitted, Fluz creates a durable approval record and notifies configured approvers. If approved, the underlying action runs automatically (for example, purchasing a gift card or transferring funds). ## How It Works ```text theme={null} Requester Manager / Approver | | | 1. request* mutation | |------------------------------->| | | | 2. APPROVAL_CREATE webhook | |<-------------------------------| | | | 3. approvalRequests query (optional) | 4. approveApprovalRequest or declineApprovalRequest | | | 5. APPROVAL_APPROVE or | | APPROVAL_DECLINE webhook | |<-------------------------------| ``` 1. **Request** — A user with the appropriate `REQUEST_*` scope calls a `request*` mutation. The API validates the input and queues the request. The mutation returns a `messageId`, not an `approvalId`. 2. **Create** — Fluz creates a pending approval and assigns account managers as approvers. Your application receives an `APPROVAL_CREATE` webhook with the `approvalId`. 3. **List (optional)** — Approvers with `LIST_APPROVALS` can call `approvalRequests` to fetch open requests for the account. 4. **Approve or decline** — Approvers with `MANAGE_APPROVALS` call `approveApprovalRequest` or `declineApprovalRequest`. On approval, Fluz executes the requested action. 5. **Webhook** — Your application receives `APPROVAL_APPROVE` or `APPROVAL_DECLINE`. If execution fails after approval, you may receive `APPROVAL_HANDLER_ERROR`. > Requests are processed asynchronously. Poll `approvalRequests` or listen for webhooks to obtain the `approvalId` after submitting a request. ## Shared Operations These operations apply to all approval request types. ### List Pending Approvals `approvalRequests` Returns open (`PENDING`) approval requests for the authenticated account. **Required scope:** `LIST_APPROVALS` ```graphql theme={null} query { approvalRequests { approvalId accountId requesterUserId approvalType approvalCode approvalStatus approversLogic expireDate createdAt approvers { approvalApproverId approverUserId role status } } } ``` ### Approve a Request `approveApprovalRequest` Approves a pending request. On success, Fluz runs the action associated with the request (for example, creating a virtual card). **Required scope:** `MANAGE_APPROVALS` ```graphql theme={null} mutation { approveApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action error { code message } } } ``` ### Decline a Request `declineApprovalRequest` Declines a pending request. No downstream action is executed. **Required scope:** `MANAGE_APPROVALS` ```graphql theme={null} mutation { declineApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action error { code message } } } ``` ## Application Scopes Approval-related scopes must be granted at both the application level (by Fluz) and the user level (via OAuth consent or `generateUserAccessToken`). | Scope | Usage | | ----------------------------------- | ----------------------------------------------------------------------------------------------- | | `LIST_APPROVALS` | List pending approval requests; receive `APPROVAL_CREATE` and `APPROVAL_HANDLER_ERROR` webhooks | | `MANAGE_APPROVALS` | Approve or decline requests; receive `APPROVAL_APPROVE` and `APPROVAL_DECLINE` webhooks | | `REQUEST_VIRTUAL_CARD` | Submit virtual card creation requests | | `REQUEST_VIRTUAL_CARD_LIMIT_CHANGE` | Submit virtual card limit change requests | | `REQUEST_GIFT_CARD` | Submit gift card purchase requests | | `REQUEST_INTERNAL_TRANSFER` | Submit internal spend-account transfer requests | | `REQUEST_ACCOUNT_TRANSFER` | Submit account-to-account transfer requests | | `REQUEST_REIMBURSEMENT` | Submit reimbursement requests | See [Authentication](/concepts/authentication) for how scopes are granted and validated. ## Request Types | Request | Mutation | Create scope | | ------------------------- | ------------------------------- | ----------------------------------- | | Virtual card creation | `requestVirtualCard` | `REQUEST_VIRTUAL_CARD` | | Virtual card limit change | `requestVirtualCardLimitChange` | `REQUEST_VIRTUAL_CARD_LIMIT_CHANGE` | | Gift card purchase | `requestGiftCardPurchase` | `REQUEST_GIFT_CARD` | | Internal transfer | `requestInternalTransfer` | `REQUEST_INTERNAL_TRANSFER` | | Account transfer | `requestAccountTransfer` | `REQUEST_ACCOUNT_TRANSFER` | | Reimbursement | `requestReimbursement` | `REQUEST_REIMBURSEMENT` | Each request type has a dedicated guide with input fields, examples, and webhook identifiers. ## Webhooks Configure webhooks in the Fluz developer portal. Approval events use the same delivery mechanism as other Fluz webhooks. See the [Developers overview](/developers) for setup and verification. | Event | When it fires | Webhook subscription scope | | ------------------------ | ---------------------------------------------- | -------------------------- | | `APPROVAL_CREATE` | A new approval request is created | `LIST_APPROVALS` | | `APPROVAL_APPROVE` | A request is approved | `MANAGE_APPROVALS` | | `APPROVAL_DECLINE` | A request is declined | `MANAGE_APPROVALS` | | `APPROVAL_HANDLER_ERROR` | Approval succeeded but action execution failed | `LIST_APPROVALS` | ### APPROVAL\_CREATE payload ```json theme={null} { "approvalId": "07df5653-43a8-4532-9881-3ab5857bbe12", "accountId": "b1155504-ad30-4b2f-873d-b8795277b128", "userId": "c3d4e5f6-a7b8-9012-cdef-345678901234", "approvalType": "GIFT_CARD", "approvalCode": "400007", "approvalStatus": "PENDING", "createdAt": "2025-07-09T10:00:00Z" } ``` Use `approvalType` and `approvalCode` together to identify the request type. Each per-type guide lists the values for that request. ### APPROVAL\_APPROVE / APPROVAL\_DECLINE payload ```json theme={null} { "approvalId": "07df5653-43a8-4532-9881-3ab5857bbe12", "accountId": "b1155504-ad30-4b2f-873d-b8795277b128", "userId": "c3d4e5f6-a7b8-9012-cdef-345678901234", "approvalType": "GIFT_CARD", "approvalCode": "400007", "approvalStatus": "APPROVED", "approvalAction": "APPROVE", "approverAccountId": "d4e5f6a7-b8c9-0123-def4-567890123456", "approverUserId": "e5f6a7b8-c9d0-1234-ef56-789012345678", "linkId": "f6a7b8c9-d0e1-2345-f678-901234567890", "attemptedAt": "2025-07-09T10:05:00Z" } ``` For declines, `approvalAction` is `DECLINE` and `approvalStatus` is `DECLINED`. ### APPROVAL\_HANDLER\_ERROR payload Sent when a request is approved but the downstream action fails (for example, insufficient balance at execution time). ```json theme={null} { "approvalId": "07df5653-43a8-4532-9881-3ab5857bbe12", "accountId": "b1155504-ad30-4b2f-873d-b8795277b128", "userId": "c3d4e5f6-a7b8-9012-cdef-345678901234", "approvalType": "GIFT_CARD", "approvalCode": "400007", "approvalStatus": "APPROVED", "approvalAction": "APPROVE", "failureStage": "ACTION_EXECUTION", "error": "Insufficient balance", "failedAt": "2025-07-09T10:05:01Z" } ``` ## Authentication Approval mutations support Bearer token (user OAuth) and Basic Auth (application API key), depending on your integration pattern. The authenticated principal becomes the requester. Use a User Access Token with the required scopes. See the [Authentication & Authorization Guide](/concepts/authentication). ## Error Handling Request mutations return a `RequestApprovalResponse`: ```json theme={null} { "data": { "requestGiftCardPurchase": { "success": true, "messageId": "1234567890" } } } ``` On failure: ```json theme={null} { "data": { "requestGiftCardPurchase": { "success": false, "messageId": null, "error": { "code": "APPROVAL_REQUEST_FAILED", "message": "Input validation error: ..." } } } } ``` Approve and decline mutations return `success: false` with an `error` object when the action cannot be completed (for example, approval not found or already resolved). # Authorized User Overview Source: https://docs.fluz.app/features/authorized-user-overview Give other Fluz users scoped access to your account, control what they can do with roles, and issue virtual cards on their behalf. An **authorized user** is an existing Fluz user who has been granted one or more roles on your account. Authorized users let a business put a team on one account — a bookkeeper who can only view activity, a manager who can approve spend, a contractor who can spend from a single card — without sharing a login or handing over the full account balance. Authorized user management is a small, self-contained part of the API: one mutation to add, one query to list, one mutation to remove, and an `authUserId` you can pass into virtual card operations to issue cards on someone else's behalf. **Adding an authorized user does not create a Fluz user.** `addAuthorizedUser` looks up an **existing** Fluz user by email or phone and creates a role assignment for them on your account. If the person does not have a Fluz account yet, register them first with [User Registration](/user-registration), then add them. *** ## How Access Is Scoped The target account is always resolved from the caller's credentials — there is no account ID parameter, and no way to manage authorized users on an account you do not control: | Authentication | Account acted on | | -------------------------- | ---------------------------------------------- | | Bearer (user access token) | The account the token was issued for. | | Basic (``) | The application's configured operator account. | Every authorized user is identified by an **`authUserId`** — the ID of the role assignment, not the user. You get it back from `addAuthorizedUser` and from `authorizedUsers`, and you pass it to `removeAuthorizedUser`, `addVirtualCardAddress`, and `createVirtualCard`. *** ## Roles Roles are assigned with the `UACRoleType` enum. An authorized user can hold more than one role, and the effective access is the highest of them. | Role | Assignable | Typical use | | --------- | ---------- | ----------------------------------------------------------------------------- | | `OWNER` | No | The account holder. Cannot be assigned or removed through the API. | | `ADMIN` | Yes | Full management of the account, its funds, cards, and other authorized users. | | `MANAGER` | Yes | Oversight of spend — reviewing and approving requests from other users. | | `SPENDER` | Yes | Day-to-day spending on the cards and spend accounts they have access to. | | `VIEWER` | Yes | Read-only visibility into account activity. | Roles are set at the time you add the user. To change someone's roles, remove the assignment and add it again with the new role set. *** ## Status Lifecycle Every role assignment carries a `UACRoleStatusType` status: | Status | Meaning | | ---------- | ---------------------------------------------------------------------------------- | | `PENDING` | The invite has been created and is waiting for the user to accept. | | `ACTIVE` | The user has access to the account with the assigned roles. | | `DECLINED` | The user rejected the invite. | | `INACTIVE` | Access has been revoked, either by removal or by the assignment being deactivated. | ```mermaid theme={null} stateDiagram-v2 [*] --> PENDING PENDING --> ACTIVE PENDING --> DECLINED ACTIVE --> INACTIVE INACTIVE --> ACTIVE DECLINED --> ACTIVE ``` By default, `addAuthorizedUser` creates the assignment as `PENDING` and sends an invite. If your product already handles its own consent flow, you can set `status: ACTIVE` and `sendInvite: false` to provision the user immediately with no invite and no acceptance step. Re-adding a user whose assignment is `INACTIVE` or `DECLINED` reactivates that assignment with the new roles rather than creating a duplicate. **`PENDING` assignments cannot be used yet.** An `authUserId` must be `ACTIVE` before you can save a billing address or create a virtual card for that user. If `addAuthorizedUser` returns `PENDING`, wait for acceptance before calling `addVirtualCardAddress` or `createVirtualCard`. *** ## What You Can Do | Action | Query / Mutation | Scope | Description | | ------------------------------------------------------------------------------------------------- | ---------------------- | -------------------- | -------------------------------------------------------------------------- | | [Create an authorized user](/features/create-authorized-users) | `addAuthorizedUser` | `MANAGE_SUBUSERS` | Grant an existing Fluz user one or more roles on your account. | | [Query authorized users](/features/query-authorized-user) | `authorizedUsers` | `VIEW_SUBUSERS` | List everyone with a role assignment on your account, optionally filtered. | | [Remove an authorized user](/features/remove-authorized-user) | `removeAuthorizedUser` | `MANAGE_SUBUSERS` | Revoke access by deactivating the role assignment. | | [Create a virtual card for an authorized user](/features/create-virtual-card-for-authorized-user) | `createVirtualCard` | `CREATE_VIRTUALCARD` | Issue a card in the authorized user's name that stays on your account. | `addAuthorizedUser`, `authorizedUsers`, and `removeAuthorizedUser` accept both Bearer and Basic authentication. `createVirtualCard` requires a Bearer token. *** ## Issuing Cards for Authorized Users A virtual card created with `input.authUserId` is issued against the authorized user's cardholder record — the card carries their name and returns their `userId` — while the card itself, its funding, and its transactions remain on your account. This is how you put a card in an employee's or contractor's hands without opening a separate account for them. The full sequence is: Skip this if they already have a Fluz account. Otherwise call `registerUser`. Call `addAuthorizedUser` and hold on to the returned `authUserId`. Continue only once the status is `ACTIVE`. Call `addVirtualCardAddress` with the `authUserId` to store a billing address for that cardholder. Call `createVirtualCard` with `input.authUserId` and the returned `userAddressId`. See [Create Virtual Card for Authorized User](/features/create-virtual-card-for-authorized-user) for the complete request and response reference. *** ## Error Handling The authorized user operations return errors **in the response data**, not as GraphQL errors. Always check `success` and read the `error` object when it is `false`: ```json theme={null} { "data": { "addAuthorizedUser": { "success": false, "authUserId": null, "error": { "code": "AUTH-0034", "message": "No Fluz user found with the provided email or phone number." } } } } ``` The codes you are most likely to hit: | Code | When it happens | | ----------- | ------------------------------------------------------------------------------------ | | `AUTH-0031` | The token is missing `MANAGE_SUBUSERS` or `VIEW_SUBUSERS`. | | `AUTH-0034` | No Fluz user matches the email or phone, or the `authUserId` is not on your account. | | `AUTH-0035` | The user already has an active role assignment on your account. | | `AUTH-0036` | You tried to remove the account owner. | | `AUTH-0037` | A general failure occurred managing the role assignment. Retry or contact support. | `createVirtualCard` behaves differently — it raises standard GraphQL errors, including `VC-0020` when a billing address is still pending issuer approval. See [Virtual Card Error Codes](/features/virtual-card-error-codes). *** ## Requirements Listing authorized users requires the `VIEW_SUBUSERS` scope. Adding and removing them requires `MANAGE_SUBUSERS`. Issuing a card on their behalf requires `CREATE_VIRTUALCARD`. Confirm your user access token carries the right scopes before calling these operations — see [Application Scopes](/fluz-dashboard/application-scopes). *** **Want to learn more?** Speak with our experts for more info or to request a demo. # Billing Address Overview Source: https://docs.fluz.app/features/billing-address Save a billing address for virtual card issuance on the caller's account. The address is saved as a `BILLING` user address and can later be passed to `createVirtualCard` as `userAddressId`. When `authUserId` is provided, the address is saved for that authorized user's underlying user while remaining attached to the caller's account. The `authUserId` must be an ACTIVE non-owner authorized user assignment on the caller's account. If an identical billing address already exists for the target cardholder on the caller's account, the existing address is returned instead of creating a duplicate. Matching is done on address fields, account, target user, and `BILLING` type. **Prerequisites:** a Bearer token with the `CREATE_VIRTUALCARD` scope. **The billing address must be verifiable** This mutation stores the billing address. It must be a real, deliverable **US** address (`US`, `USA`, or `United States`) with a correct city, state, and ZIP, and **PO boxes are not accepted**. The USPS/Smarty check runs when a virtual card is created — an address that can't be verified there causes card creation to fail with `VC-0025`. See [Address Formatting Requirements](/concepts/address-formatting-requirements) for the full field rules and examples. ## How it fits together 1. Save an address with `addVirtualCardAddress` — you get back a `userAddressId`. 2. Pass that `userAddressId` to [`createVirtualCard`](/features/create-card). It takes precedence over any inline `billingAddress`. 3. Reuse the same `userAddressId` across as many cards as you like — the address is stored once on the account. You can also skip pre-saving entirely and pass a `billingAddress` object inline on `createVirtualCard`; a matching saved address will be reused, or a new one created. ## Next steps The full mutation contract — parameters, responses, examples, and error codes. Put the saved `userAddressId` to work issuing a card. # Card Linked Offers Source: https://docs.fluz.app/features/card-linked-offers This page details how to retrieve and understand Card Linked Offers (CLOs) using the `getMerchants` query. CLOs are identified by the `type` field value `CARD_LINKED_OFFER` and are often tied to usage of the Fluz Virtual Card — sometimes redeemable exclusively through it. They use a distinct structure, `cloDetails`, for their rate and condition information, differing significantly from standard Gift Card offers. **Prerequisites:** a user access token with the `LIST_OFFERS` scope. Full query contract: [API Reference](/api-reference/queries/get-merchants). To fetch *only* CLOs, set the `offerTypes` input argument to `{ cardLinkedOffer: true, giftCardOffer: false }`. ## Detailed query for Card Linked Offers This query requests fields specifically relevant to CLOs, focusing on the `cloDetails` object. Fields like `offerRates` and `stockInfo`, which are pertinent to Gift Cards, are typically `null` or empty for CLOs and can often be omitted from the query for CLOs. ```graphql theme={null} query GetCLOMerchants( $name: String, $paginate: OffsetInput, $offerTypes: OfferTypesInput ) { getMerchants( name: $name, paginate: $paginate, offerTypes: $offerTypes ) { merchantId name slug offers { offerId type # Will be CARD_LINKED_OFFER hasStockInfo # Usually false for CLOs denominationsType # Often VARIABLE for CLOs # --- Offer Rates (Usually null/empty for CLOs) --- # offerRates { maxUserRewardValue } # Can query, but expect null # --- Stock Info (Usually null/empty for CLOs) --- # stockInfo { ... on StockInfoVariableType { __typename } } # Usually empty # --- CLO Details (Relevant for CLOs) --- cloDetails { currentRateType regularRate promoRate promoBaseRate promoMaxCap applyPromoBaseRateAfterCap minimumPurchaseAmount activePeriodStartDate activePeriodEndDate periods { id offerRateType periodType startDate endDate startTime endTime daysOfWeek daysOfMonth } } } } } # Example Variables to pass with the query: # { # "paginate": { "limit": 20, "offset": 0 }, # "offerTypes": { "cardLinkedOffer": true, "giftCardOffer": false } # } ``` ## Sample response Here's an example response you will get from the `getMerchants` query with CLO Offers Only: ```json JSON theme={null} { "data": { "getMerchants": [ { "merchantId": "4b4195cb-ba98-49e6-8935-761458a76cdd", "name": "REI", "slug": "REI", "offers": [ { "offeringMerchantId": "4b4195cb-ba98-49e6-8935-761458a76cdd", "offerId": "3edd79b7-aa46-4bd6-96de-b78e4f3a79e0", "type": "CARD_LINKED_OFFER", "hasStockInfo": false, "denominationsType": "VARIABLE", "stockInfo": [], "offerRates": null, "cloDetails": { "currentRateType": "PROMO", "regularRate": 22, "promoRate": 25, "promoBaseRate": 20, "promoMaxCap": 1523, "applyPromoBaseRateAfterCap": false, "minimumPurchaseAmount": null, "activePeriodStartDate": null, "activePeriodEndDate": null, "periods": [ { "id": "0cb1fde3-cfb5-4f31-b75f-eedf55d65ca7", "offerRateType": "PROMO", "periodType": "ALWAYS_AVAILABLE", "startDate": null, "endDate": null, "startTime": null, "endTime": null, "daysOfWeek": null, "daysOfMonth": null }, { "id": "21f57686-4139-4392-8eca-00cffad24fa1", "offerRateType": "REGULAR", "periodType": "ALWAYS_AVAILABLE", "startDate": null, "endDate": null, "startTime": null, "endTime": null, "daysOfWeek": null, "daysOfMonth": null } ] } }, { "offeringMerchantId": "4b4195cb-ba98-49e6-8935-761458a76cdd", "offerId": "7ec04538-ad28-4d49-8181-f4ec108facc1", "type": "CARD_LINKED_OFFER", "hasStockInfo": false, "denominationsType": "VARIABLE", "stockInfo": [], "offerRates": null, "cloDetails": { "currentRateType": "PROMO", "regularRate": 4, "promoRate": 17, "promoBaseRate": 12, "promoMaxCap": 1999, "applyPromoBaseRateAfterCap": false, "minimumPurchaseAmount": null, "activePeriodStartDate": "2025-04-21", "activePeriodEndDate": "2025-04-27", "periods": [ { "id": "9f887399-7bd6-44ad-af04-3fccdafeb092", "offerRateType": "PROMO", "periodType": "MULTIPLE_DAYS", "startDate": "2025-04-21", "endDate": "2025-04-27", "startTime": null, "endTime": null, "daysOfWeek": null, "daysOfMonth": null }, { "id": "3769f838-f0a8-4971-9415-6bb15eae64be", "offerRateType": "REGULAR", "periodType": "ALWAYS_AVAILABLE", "startDate": null, "endDate": null, "startTime": null, "endTime": null, "daysOfWeek": null, "daysOfMonth": null } ] } } ] } ] } } ``` ## Card Linked Offer Specific Fields Explained When `offer.type` is `CARD_LINKED_OFFER`, the primary source of information is the `cloDetails` object within the `Offer`. * **`type`** (`String!`): Will always be `CARD_LINKED_OFFER`. * **`cloDetails`** (`CloDetails`): This complex object contains all the specific rate, promotion, timing rules, and conditions for the Card Linked Offer. Returns `null` for non-CLO types. See the tables below for a breakdown of its fields. * **`offerRates`**: Typically `null` or empty for CLOs. The rate information is contained within `cloDetails`. * **`stockInfo`**: Typically empty for CLOs as they are usually linked to card usage rather than pre-defined stock. * **`hasStockInfo`**: Usually `false` for CLOs. * **`denominationsType`**: Often `VARIABLE` for CLOs, reflecting that the offer applies to a transaction value rather than a fixed gift card amount. ### Understanding the `CloDetails` Object This object provides the specific rate structure for Card Linked Offers. | Field Name | Type | Description | | :--------------------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `currentRateType` | CloRateTypeEnum! | Indicates if the `REGULAR` or `PROMO` rate is currently active, determined by evaluating all `periods` against the current time. (`REGULAR`, `PROMO`) | | `regularRate` | Float | The standard reward rate applied when no promotion is active. | | `promoRate` | Float | The promotional reward rate applied when a `PROMO` period is active and the purchase amount is below or equal to `promoMaxCap` (if applicable). | | `promoBaseRate` | Float | The reward rate applied during a `PROMO` period *after* the `promoMaxCap` has been exceeded, *if*`applyPromoBaseRateAfterCap` is true. | | `promoMaxCap` | Float | The maximum purchase amount (currency value) up to which the `promoRate` applies during a `PROMO` period. Purchases above this amount may earn `promoBaseRate` or the `regularRate`, depending on flags. | | `applyPromoBaseRateAfterCap` | Boolean | If `true`, the `promoBaseRate` is applied to the portion of the purchase amount *exceeding* the `promoMaxCap` during an active `PROMO` period. If `false`, amounts over the cap might earn no reward or the `regularRate`. | | `minimumPurchaseAmount` | Float | The minimum transaction value required to qualify for *any* reward from this CLO, if specified. | | `activePeriodStartDate` | String | The start date (YYYY-MM-DD) of the *currently active* offer period (could be regular or promo), if the active period has defined start/end dates. `null` if always active or based on other criteria. | | `activePeriodEndDate` | String | The end date (YYYY-MM-DD) of the *currently active* offer period, if applicable. `null` if always active or open-ended. | | `periods` | \[CloPeriod!]! | A list of *all* defined time periods associated with this offer. The system evaluates these periods to determine the `currentRateType`. See the `CloPeriod` table below. | ### Understanding the `CloPeriod` Object (within `CloDetails.periods`) Each object in the `periods` array defines a specific time window and the rate type (`REGULAR` or `PROMO`) associated with it. The combination of these periods determines the offer's availability and active rate. | Field Name | Type | Description | | :-------------- | :--------------- | :---------------------------------------------------------------------------------------------------------------------------- | | `id` | UUID! | Unique identifier for this specific period definition. | | `offerRateType` | CloRateTypeEnum! | Indicates if this period corresponds to the `REGULAR` or `PROMO` rate structure defined in the parent `CloDetails`. | | `periodType` | String | Describes the nature of the period's timing (e.g., `ALWAYS_AVAILABLE`, `MULTIPLE_DAYS`, `SPECIFIC_DAY`, `RECURRING_WEEKLY`). | | `startDate` | String | Start date (YYYY-MM-DD) when this period rule becomes potentially active. | | `endDate` | String | End date (YYYY-MM-DD) when this period rule ceases to be active. | | `startTime` | String | Start time (e.g., "HH:MM" in UTC or a defined timezone) for daily applicability, often used with `daysOfWeek`. | | `endTime` | String | End time (e.g., "HH:MM" in UTC or a defined timezone) for daily applicability. | | `daysOfWeek` | \[String] | Array of days (e.g., \["MONDAY", "FRIDAY"]) when this period rule is active. Used for weekly recurring periods. | | `daysOfMonth` | \[String] | Array of specific days of the month (e.g., \["1", "15"]) when this period rule is active. Used for monthly recurring periods. | ## Next steps Issue the card that these offers reward — CLO earnings apply to its spend. Adjust limits, funding, and lifecycle settings on an existing card. # Close Spend Accounts Source: https://docs.fluz.app/features/close-spend-accounts # Overview Each cash balance account can be closed, with all it's balance transferred and depending virtual cards' funding source changed or them being locked. # Close User Cash Balance Account ## Sample request You can close a user cash balance account with the `closeUserCashBalance` mutation. This mutation allows you to do the following: * Close your spending account. * Change virtual cards funding source if there are any that have this spending account as one. * Lock related virtual cards. * Transfer the remaining balance to another spend account. The query takes a `CloseUserCashBalanceInput` input object. ```graphql theme={null} mutation closeUserCashBalance($input: CloseUserCashBalanceInput!) { closeUserCashBalance(input: $input) { closedUserCashBalance { userCashBalanceId lifetimeCashBalance nickname status createdAt closedAt } affectedVirtualCards { virtualCardId virtualCardLast4 } } } ``` This mutation requires the `CloseUserCashBalanceInput` input type. Any field marked with an exclamation mark (`!`) in the schema is mandatory and must be included in the request. ## CloseUserCashBalanceInput | Field name | Type | Description | | :------------------------ | :-------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | idempotencyKey | UUID! | A unique client-generated UUID to ensure a request is processed only once. | | userCashBalanceId | UUID! | An unique identifier for the cash balance account. | | newFundingSource | CloseUserCashBalanceFundingSource | A new funding source for all dependent virtual cards. Required only if there are virtual cards tied to this cash balance account and `lockAllVirtualCards` is not provided. | | lockAllVirtualCards | Boolean | Whether to lock all dependent virtual cards. Required only if there are virtual cards tied to this cash balance account and `newFundingSource` is not provided. | | transferUserCashBalanceId | UUID | An unique identifier for the transfer. Required only if cash balance account's available balance is >0. | ### Example ```json theme={null} { "idempotencyKey": "65666780-c552-47f0-ba05-f90fced0ef9f", "userCashBalanceId": "ca8e1ccf-b42d-41d3-8cad-2237e020d1b1", "lockAllVirtualCards": true, "transferUserCashBalanceId": "6d1b4b19-deef-42f5-80d7-ec34804ce091" } ``` ## CloseUserCashBalanceFundingSource | Field name | Type | Description | | :----------------------------------------------------------- | :----------------------- | :------------------------------------------------------------------------------------------- | | bankAccountId | UUID | An unique identifier for the bank account. Required if other options aren't present. | | userCashBalanceId | UUID | An unique identifier for the cash balance account. Required if other options aren't present. | | primaryFundingSource: | VirtualCardFundingSource | A type of a primary funding source, either `FLUZ_BALANCE` | | or `BANK_ACCOUNT`. Required if other options aren't present. | | | ### Example ```json theme={null} { "userCashBalanceId": "ca8e1ccf-b42d-41d3-8cad-2237e020d1b1", } ``` ## Sample response The response from the `closeUserCashBalance` mutation will include a closed cash balance account details, including its unique identifier and a list of affected virtual cards. ```json theme={null} { "data": { "closeUserCashBalance": { "closedUserCashBalance": { "userCashBalanceId": "ca8e1ccf-b42d-41d3-8cad-2237e020d1b1", "lifetimeCashBalance": "0.00000", "nickname": "Closed Spend Account", "status": "CLOSED", "createdAt": "2025-12-30T21:18:26.782Z", "closedAt": "2025-12-30T21:22:29.359Z" }, "affectedVirtualCards": [ { "virtualCardId": "afad4d6d-11f0-4ac1-b971-caca7c34c77c", "virtualCardLast4": "4473" } ] } } } ``` ### Response Fields | Field name | Type | Description | | :------------------- | :----------------------------------------- | :----------------------------------------------- | | closeUserCashBalance | ClosedUserCashBalance! | Closed cash balance account details. | | lifetimeCashBalance | \[CloseUserCashBalanceResponseVirtualCard] | A list of virtual cards affected by the closure. | #### closeUserCashBalance | Field name | Type | Description | | :------------------ | :--------------------- | :---------------------------------------------------------- | | userCashBalanceId | UUID! | Unique identifier for the cash balance account. | | lifetimeCashBalance | String! | Cumulative total of all funds ever deposited (starts at 0). | | nickname | String! | The custom name assigned to the account. | | status | UserCashBalanceStatus! | Current status of the account (should be `CLOSED`). | | createdAt | DateTime! | Timestamp when the account was created. | | closedAt | DateTime! | Timestamp when the account was closed. | #### affectedVirtualCards | Field name | Type | Description | | :--------------- | :---- | :----------------------------------------------- | | virtualCardId | UUID! | Unique identifier for the virtual card. | | virtualCardLast4 | UUID! | The last four digits of the virtual card number. |
> ❗️ Authorization required > > This mutation requires the `MANAGE_PAYMENT` scope. Ensure your access token has been granted this scope before attempting to create a cash balance account. # Create Authorized User Source: https://docs.fluz.app/features/create-authorized-users Add an existing Fluz user to the caller's account as an authorized user with one or more access roles. This mutation does not create a user — it looks up an existing Fluz user by email or phone, then creates a role assignment on the caller's account (or reactivates a previously `DECLINED`/`INACTIVE` assignment with the new roles). The target account is always resolved from the caller's credentials — Bearer tokens use the token's account; Basic (API key) callers use the application's configured operator account. There is no way to target an account you do not own through this endpoint. 🔒 Restricted Access This mutation requires the `MANAGE_SUBUSERS` scope. It supports both Bearer (user access token) and Basic (``) authentication. The `OWNER` role cannot be assigned through this endpoint. ```graphql theme={null} mutation AddAuthorizedUser( $email: String $phone: String $roles: [UACRoleType!]! $status: UACRoleStatusType $sendInvite: Boolean ) { addAuthorizedUser( email: $email phone: $phone roles: $roles status: $status sendInvite: $sendInvite ) { success authUserId roles status pendingActionId error { code message } } } ```
### Parameters | Parameter | Type | Required | Description | | :--------- | :---------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | email | String | No\* | Email address of the existing Fluz user to authorize. \*At least one of `email` or `phone` is required. | | phone | String | No\* | Phone number of the existing Fluz user to authorize. \*At least one of `email` or `phone` is required. | | roles | \[UACRoleType!]! | Yes | One or more roles to assign. Allowed values: `ADMIN`, `MANAGER`, `SPENDER`, `VIEWER`. `OWNER` is not allowed. | | status | UACRoleStatusType | No | The initial status for the role assignment. Defaults to `PENDING`. Set to `ACTIVE` to skip the pending state and activate the assignment immediately (no acceptance required). Allowed values: `PENDING`, `ACTIVE`, `INACTIVE`, `DECLINED`. | | sendInvite | Boolean | No | Whether to send the role assignment invite to the user. Defaults to `true`. Set to `false` to create the assignment without sending an invite, making the invitation optional. |
### Response #### Success Response ```json theme={null} { "data": { "addAuthorizedUser": { "success": true, "authUserId": "8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d", "roles": ["MANAGER", "VIEWER"], "status": "PENDING", "pendingActionId": "2f7c1a3b-9e44-4d2a-8a91-c1b2d3e4f5a6", "error": null } } } ```
### Response Fields | Field | Type | Description | | :---------------- | :------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------- | | `success` | Boolean | `true` if the role assignment was successfully created or reactivated. | | `authUserId` | UUID | The authorized user ID (UAC role assignment ID). Use this value when calling `removeAuthorizedUser` or filtering results from `authorizedUsers`. | | `roles` | \[UACRoleType] | The roles assigned to the user on this account. | | `status` | UACRoleStatusType | Status of the role assignment: `PENDING`, `ACTIVE`, `INACTIVE`, or `DECLINED`. | | `pendingActionId` | UUID | The pending action ID for the invite, if one was created (returned when the assignment requires user acceptance). | | `error` | AuthorizedUserError | If `success` is false, an Error object containing `code` and `message`. |
> Note: This mutation returns errors in the response data, not as GraphQL errors. Always check the `success` field and handle the `error` object when `success` is false. ### Example Request ```curl theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation AddAuthorizedUser($email: String, $phone: String, $roles: [UACRoleType!]!) { addAuthorizedUser(email: $email, phone: $phone, roles: $roles) { success authUserId roles status pendingActionId error { code message } } }", "variables": { "email": "teammate@example.com", "roles": ["MANAGER", "VIEWER"] } }' ``` ```typescript theme={null} const response = await fetch('https://transactional-graph.staging.fluzapp.com/api/v1/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, body: JSON.stringify({ query: ` mutation AddAuthorizedUser( $email: String $phone: String $roles: [UACRoleType!]! ) { addAuthorizedUser( email: $email phone: $phone roles: $roles ) { success authUserId roles status pendingActionId error { code message } } } `, variables: { email: "teammate@example.com", roles: ["MANAGER", "VIEWER"] } }) }); const data = await response.json(); if (data.data.addAuthorizedUser.success) { console.log('Authorized user added:', data.data.addAuthorizedUser); } else { console.error('Add authorized user failed:', data.data.addAuthorizedUser.error); } ```
#### Skip the pending state and suppress the invite To activate the authorized user immediately without sending an invite, set `status` to `ACTIVE` and `sendInvite` to `false`. The assignment is created in the `ACTIVE` state, no `pendingActionId` is returned, and no invite is sent to the user. ```curl theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation AddAuthorizedUser($email: String, $phone: String, $roles: [UACRoleType!]!, $status: UACRoleStatusType, $sendInvite: Boolean) { addAuthorizedUser(email: $email, phone: $phone, roles: $roles, status: $status, sendInvite: $sendInvite) { success authUserId roles status pendingActionId error { code message } } }", "variables": { "email": "teammate@example.com", "roles": ["MANAGER", "VIEWER"], "status": "ACTIVE", "sendInvite": false } }' ``` ### Error Codes | Code | Message | Description | | :---------- | :--------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ARG-0002` | Missing required arguments | Neither `email` nor `phone` was provided, or `roles` is empty. At least one identifier and at least one role are needed. | | `ARG-0001` | Invalid arguments received | One or more values in `roles` are not allowed (for example, `OWNER`), or `status` is not one of `PENDING`, `ACTIVE`, `INACTIVE`, `DECLINED`. Use `ADMIN`, `MANAGER`, `SPENDER`, or `VIEWER` for roles. | | `AUTH-0008` | Invalid user access | The caller could not be resolved from the access token or API key, or the Basic-auth application has no operator account configured. Verify your authentication credentials. | | `AUTH-0031` | The requested scopes must be granted by the user first. | The token is missing the `MANAGE_SUBUSERS` scope required to manage authorized users. | | `AUTH-0034` | No Fluz user found with the provided email or phone number. | No existing Fluz user matches the supplied `email` or `phone`. The target user must already have a Fluz account. | | `AUTH-0035` | This user already has an active role assignment on this account. | The user is already authorized on the caller's account. Use `removeAuthorizedUser` first if you need to reassign roles. | | `AUTH-0037` | Unable to manage authorized user. Please try again or contact support. | A general failure occurred while creating the role assignment. Please retry or contact support. |
# Create Spend Accounts Source: https://docs.fluz.app/features/create-spend-accounts # Overview A user cash balance account is a spending account that allows users to manage and track their available funds within the Fluz platform. Each cash balance account is identified by a unique nickname and maintains separate balances for total, available, and lifetime deposits. Users can create multiple cash balance accounts (e.g., "Team Travel", "Operations Wallet", "Marketing Budget") to organize their spending across different purposes. Cash balance accounts are required for making deposits, purchasing gift cards, and funding virtual card transactions on the Fluz platform. # Create User Cash Balance Account ## Sample request You can create a new user cash balance account with the `createUserCashBalance` mutation. This mutation allows you to set up a new spending account with a custom nickname to help organize your funds. The query takes a `CreateUserCashBalanceInput` input object. ```json theme={null} { "query": "mutation createUserCashBalance($input: CreateUserCashBalanceInput!) { createUserCashBalance(input: $input) { userCashBalanceId totalCashBalance availableCashBalance lifetimeCashBalance nickname status createdAt }}", "variables": { "input": { "nickname": "Team Travel" } } } ``` This mutation requires the `CreateUserCashBalanceInput` input type. Any field marked with an exclamation mark (`!`) in the schema is mandatory and must be included in the request. | Field name | Type | Description | | :--------- | :------ | :------------------------------------------------------------------------------------------ | | nickname | String! | A custom name for the cash balance account. This helps identify the purpose of the account. | ## CreateUserCashBalanceInput ```json theme={null} { "nickname": "Team Travel" } ``` ## Sample response The response from the `createUserCashBalance` mutation will include the newly created cash balance account details, including its unique identifier and initial balance information. ```json theme={null} { "data": { "createUserCashBalance": { "userCashBalanceId": "f8a3c9e1-7b2d-4f5e-9c8a-1d2e3f4g5h6i", "totalCashBalance": "0", "availableCashBalance": "0", "lifetimeCashBalance": "0", "nickname": "Team Travel", "status": "ACTIVE", "createdAt": "2025-01-15T10:30:00.000Z" } } } ``` ### Response Fields | Field name | Type | Description | | :------------------- | :--------------------- | :----------------------------------------------------------- | | userCashBalanceId | UUID! | Unique identifier for the cash balance account | | totalCashBalance | String! | Total cash balance in the account (starts at 0) | | availableCashBalance | String! | Available cash balance for immediate use (starts at 0) | | lifetimeCashBalance | String! | Cumulative total of all funds ever deposited (starts at 0) | | nickname | String | The custom name assigned to the account | | status | UserCashBalanceStatus! | Current status of the account (ACTIVE, CLOSED, or SUSPENDED) | | createdAt | DateTime! | Timestamp when the account was created | > ❗️ Authorization required > > This mutation requires the `MANAGE_PAYMENT` scope. Ensure your access token has been granted this scope before attempting to create a cash balance account. # Issue Cards Source: https://docs.fluz.app/features/create-virtual-card Use the `createVirtualCard` mutation to create a virtual card with configurable funding sources, limits, and lifecycle controls. **Prerequisites:** a user access token with the `CREATE_VIRTUALCARD` scope, and a `CreateVirtualCardInput` object. To find an `offerId`, see [Get Virtual Card Offers](/features/get-card-offers). **Billing address must be verifiable.** The billing address — whether passed inline as `billingAddress` or referenced by `userAddressId` — must be a real, deliverable **US** address (`US`, `USA`, or `United States`) with a correct city, state, and ZIP; **PO boxes are not accepted**. It is validated against USPS data (via Smarty) as the cardholder is set up. If it can't be verified, the card is not created and the request fails with `VC-0025` (`UnableToCreateAuthUser`), returned on `extensions.code`. See [Address Formatting Requirements](/concepts/address-formatting-requirements). ## Sandbox test offers | Offer ID | Program Name | Reward Value | | -------------------------------------- | ---------------------------------------------- | ------------ | | `ed669305-5e43-40a0-9a25-7a15ed174628` | Virtual Card | 1.5% | | `b23630f6-8d91-43df-84aa-a541e7691197` | Virtual Card - Mastercard Prepaid | 1.5% | | `592c394e-26cc-44ac-a145-a5f81301fe77` | Brand Locked Virtual Card - Mastercard Prepaid | 1.5% | ## Important considerations * **Funding Source:** You can fund your virtual cards using either your Fluz balance or a linked bank account as the primary funding source. * If you select a bank account as the `primaryFundingSource`, you must provide the `bankAccountId`. * **Spend Limits:** The `spendLimit` you set must adhere to the program's defined spend limits for the chosen `spendLimitDuration`. An error will occur if the limit is exceeded. * **Balance Composition:** By default, virtual cards may be funded by a combination of your specified spend account, prepaid (gift card) balance, and rewards balance when available. You can restrict the card to draw only from the spend account by setting `usePrepaymentBalance: false` and `useRewardsBalance: false` in the input. * **Transaction Annotations:** You may optionally attach a `memo` and/or `transactionCategory` at card creation time. These will be associated with the resulting transaction for tracking and organization purposes. * If `transactionCategory` is provided, a matching category will be found or created for the account. * `attachmentId` is not supported ## Arguments * `input` (`CreateVirtualCardInput!`): The input object containing details for the new virtual card. ## CreateVirtualCardInput fields | Field | Type | Description | Required | | ---------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `idempotencyKey` | `UUID!` | A unique client-generated UUID to ensure a request is processed only once. | Yes | | `spendLimit` | `Float!` | The maximum amount you can charge to the card. You are only charged for the amount actually used. | Yes | | `spendLimitDuration` | `VirtualCardSpendLimitDuration` | Card limit duration type. Default is `LIFETIME`. | No | | `lockDate` | `String` | The date when the card will be locked. The default is 47 months from creation. Format: yyyy-mm-dd | No | | `lockCardNextUse` | `Boolean` | Setting to lock the card after its next use. The default is `false`. | No | | `cardNickname` | `String` | The card's nickname. | No | | `primaryFundingSource` | `VirtualCardFundingSource` | Primary Funding Source for Virtual Card. Default is `FLUZ_BALANCE`. | No | | `bankAccountId` | `UUID` | The unique identifier for the bank account. Required if `primaryFundingSource` is `BANK_ACCOUNT`. | No | | `offerId` | `UUID` | The Offer Id of Virtual Card Offer. Use `getVirtualCardOffers` to fetch a list of active offers. | No | | `userCashBalanceId` | `UUID` | The cash balance (spend account) to be used for that purchase. | No | | `usePrepaymentBalance` | `Boolean` | When `false`, the card will not draw from the prepaid (gift card) balance. Defaults to `true`, preserving the existing behavior where prepaid funds may be used in addition to the specified spend account. | No | | `useRewardsBalance` | `Boolean` | When `false`, the card will not draw from the rewards balance. Defaults to `true`, preserving the existing behavior where rewards may be used in addition to the specified spend account. | No | | `billingAddress` | `VirtualCardBillingAddressInput` | Billing address for the virtual card. If it doesn't match an existing address on the user's account (by street, city, state, and postal code), a new one is created. All fields are required except streetAddressLine2. Only U.S. addresses are supported. If userAddressId is provided, this field is ignored. | No | | `userAddressId` | `UUID` | The ID of an existing billing address on the user's account to use for the virtual card. If provided, it takes precedence over billingAddress. The ID must reference a UserAddress belonging to the calling user. | No | | `memo` | `String` | An optional note to attach to the transaction. | No | | `transactionCategory` | `String` | An optional category name. A matching category will be found or created for the account. | No | ## VirtualCardBillingAddressInput fields | Field | Type | Description | Required | | -------------------- | --------- | ----------------------------------------------------------------------------------------------- | -------- | | `streetAddressLine1` | `String!` | The primary street address (e.g., "123 Main St"). PO boxes are not accepted by the card issuer. | Yes | | `streetAddressLine2` | `String` | Optional secondary address line (e.g., apartment, suite, or unit number). | No | | `country` | `String!` | The country name. Currently only "United States" is supported. | Yes | | `city` | `String!` | The city name. | Yes | | `state` | `String!` | The state name (e.g., "New York") or two-letter US state code (e.g., "NY"). | Yes | | `postalCode` | `String!` | A 5-digit US ZIP code. | Yes | Addresses that fail verification return `VC-0025` (`UnableToCreateAuthUser`). See [Address Formatting Requirements](/concepts/address-formatting-requirements) and [Virtual Card Error Codes](/features/virtual-card-error-codes). ## cURL Example ```bash theme={null} curl -X POST \ https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \ -d '{ "query": "mutation CreateVirtualCard { createVirtualCard( input: { spendLimit: 150.00, idempotencyKey: \"63b2c9e0-62d1-42ab-b1c2-1a7ee2f8c0a9\", offerId: \"b3355504-ad30-4b2f-873d-b8795277b918\", spendLimitDuration: MONTHLY, lockCardNextUse: false, cardNickname: \"My New Test Card\", primaryFundingSource: FLUZ_BALANCE, memo: \"Office supplies\", transactionCategory: \"Expenses\" } ) { virtualCardId userId cardholderName expiryMonth expiryYear virtualCardLast4 status cardType initialAmount usedAmount createdAt } }" }' ``` ## Sample Mutation ```graphql theme={null} mutation { createVirtualCard( input: { idempotencyKey: "07df5653-43a8-4532-9881-3ab5857bbe12" spendLimit: 123.45 spendLimitDuration: DAILY lockDate: "2030-10-10" lockCardNextUse: true cardNickname: "xyz789" primaryFundingSource: FLUZ_BALANCE offerId: "b3355504-ad30-4b2f-873d-b8795277b918" userCashBalanceId: "b1155504-ad30-4b2f-873d-b8795277b128" } ) { virtualCardId userId cardholderName expiryMonth expiryYear virtualCardLast4 status cardType initialAmount usedAmount createdAt } } ``` ## Sample Mutation — Cash-Only Balance Restrict a virtual card to draw only from the specified `userCashBalanceId`, excluding prepaid and rewards funds. ```graphql theme={null} mutation { createVirtualCard( input: { idempotencyKey: "07df5653-43a8-4532-9881-3ab5857bbe13" spendLimit: 150.00 offerId: "b3355504-ad30-4b2f-873d-b8795277b918" primaryFundingSource: FLUZ_BALANCE userCashBalanceId: "b1155504-ad30-4b2f-873d-b8795277b128" usePrepaymentBalance: false useRewardsBalance: false } ) { virtualCardId virtualCardLast4 status initialAmount } } ``` ## Sample Response ```json theme={null} { "data": { "createVirtualCard": { "virtualCardId": "07df5653-43a8-4532-9881-3ab5857bbe11", "userId": "07df5653-43a8-4532-9881-3ab5857bbe11", "cardholderName": "xyz789", "expiryMonth": "12", "expiryYear": "27", "virtualCardLast4": "7890", "status": "ACTIVE", "cardType": "MULTI_USE", "initialAmount": 150.00, "usedAmount": 0, "createdAt": "2025-07-09T10:00:00Z" } } } ``` ### Response fields | Field | Type | Description | | ------------------ | ------------------- | ----------------------------------------------------------------------------- | | `virtualCardId` | `UUID` | The unique identifier assigned to the newly created virtual card. | | `userId` | `UUID` | The unique identifier of the user who created this virtual card. | | `cardholderName` | `String` | The name of the cardholder as it appears on the virtual card. | | `expiryMonth` | `String` | The two-digit expiration month of the virtual card (e.g., "12" for December). | | `expiryYear` | `String` | The two-digit expiration year of the virtual card (e.g., "27" for 2027). | | `virtualCardLast4` | `String` | The last four digits of the virtual card number. | | `status` | `VirtualCardStatus` | The current status of the virtual card (e.g., ACTIVE, PENDING). | | `cardType` | `VirtualCardType` | Indicates if the card is MULTI\_USE or SINGLE\_USE. | | `initialAmount` | `Float` | The original spendLimit set when the card was created. | | `usedAmount` | `Float` | The total amount that has been spent using this virtual card so far. | | `createdAt` | `DateTime` | Timestamp when the virtual card was created. | ## Next steps Retrieve the PAN, CVV, and expiry so the card can actually be used. Enumerate every program available to your account to pick the right `offerId`. # Create Virtual Card for Authorized User Source: https://docs.fluz.app/features/create-virtual-card-for-authorized-user Create a virtual card on behalf of an authorized user on the caller's account. Pass the authorized user's `authUserId` (the UAC role assignment ID returned by `addAuthorizedUser`) to create the card for that user's underlying cardholder record while keeping the card scoped to the caller's account. The `authUserId` must belong to the caller's account, must be ACTIVE, and cannot be an `OWNER` assignment. If `addAuthorizedUser` returns `PENDING`, the authorized user must accept the invite before this mutation can use that `authUserId`. Virtual card creation can use a saved billing address (`userAddressId`) or an inline `billingAddress`. For select offers, providing either address value triggers billing-address registration with the card issuer before the card is issued. If issuer approval is still pending after the server-side wait window, the mutation returns GraphQL error code `VC-0020` with `extensions.addressId`; retry with that value as `userAddressId`. 🔒 Restricted Access This mutation requires a Bearer token with the `CREATE_VIRTUALCARD` scope. ```graphql theme={null} mutation CreateVirtualCard($input: CreateVirtualCardInput!) { createVirtualCard(input: $input) { virtualCardId userId cardholderName expiryMonth expiryYear virtualCardLast4 status cardType initialAmount usedAmount createdAt authorizationSetting { lockDate dailySpendLimit weeklySpendLimit monthlySpendLimit } } } ```
### Parameters | Parameter | Type | Required | Description | | :------------------------- | :----------------------------- | :------- | :--------------------------------------------------------------------------------------------------------- | | input | CreateVirtualCardInput! | Yes | Wrapper object for the virtual card creation request. | | input.idempotencyKey | UUID! | Yes | Unique client-generated UUID. The same key prevents the same request from being processed more than once. | | input.spendLimit | Float! | Yes | Maximum amount that can be charged to the card. Minimum is `$5`; at most two decimal places are allowed. | | input.offerId | UUID | Yes | Virtual card offer ID. Use `getVirtualCardOffers` to fetch active offers. | | input.authUserId | UUID | No | Authorized user ID (UAC role assignment ID) to create the card on behalf of. Must be ACTIVE and non-owner. | | input.userAddressId | UUID | No | Saved billing address ID for the caller or authorized cardholder. Takes precedence over `billingAddress`. | | input.billingAddress | VirtualCardBillingAddressInput | No | New billing address to save and register with the card program before issuing the card. | | input.spendLimitDuration | VirtualCardSpendLimitDuration | No | Card limit duration. Defaults to `LIFETIME`. | | input.lockDate | String | No | Future lock date. Defaults to 47 months from the request date. | | input.lockCardNextUse | Boolean | No | Whether to lock the card after the next use. Defaults to `false`. | | input.cardNickname | String | No | Card nickname. Must be 1-50 characters when provided. | | input.primaryFundingSource | VirtualCardFundingSource | No | Primary funding source. Defaults to `FLUZ_BALANCE`. If `BANK_ACCOUNT`, `bankAccountId` is required. | | input.bankAccountId | UUID | No | Bank account ID used when `primaryFundingSource` is `BANK_ACCOUNT`. | | input.userCashBalanceId | UUID | No | Spend account ID to attach to the card. | | input.usePrepaymentBalance | Boolean | No | When `false`, the card will not draw from prepaid balance. Defaults to `true`. | | input.useRewardsBalance | Boolean | No | When `false`, the card will not draw from rewards balance. Defaults to `true`. | | input.memo | String | No | Optional note attached to the transaction. Maximum 255 characters. | | input.transactionCategory | String | No | Optional category name. A matching category is found or created for the account. Maximum 100 characters. |
### Response #### Success Response ```json theme={null} { "data": { "createVirtualCard": { "virtualCardId": "6d9f0d0f-21f0-4ad1-8fc7-2fb7dd58ce11", "userId": "f1320ac4-52dc-4c67-9e80-24e506b18450", "cardholderName": "Ada Lovelace", "expiryMonth": "08", "expiryYear": "2029", "virtualCardLast4": "4242", "status": "ACTIVE", "cardType": "MULTI_USE", "initialAmount": 100, "usedAmount": 0, "createdAt": "2026-05-15T15:18:00.000Z", "authorizationSetting": { "lockDate": "2029-08-15", "dailySpendLimit": null, "weeklySpendLimit": null, "monthlySpendLimit": null } } } } ```
### Response Fields | Field | Type | Description | | :--------------------- | :------- | :----------------------------------------------------------------------------------------------- | | `virtualCardId` | UUID | Created virtual card ID. | | `userId` | UUID | User ID of the cardholder. When `authUserId` is provided, this is the authorized user's user ID. | | `cardholderName` | String | Cardholder name shown on the virtual card. | | `expiryMonth` | String | Virtual card expiration month. | | `expiryYear` | String | Virtual card expiration year. | | `virtualCardLast4` | String | Last 4 digits of the virtual card number. | | `status` | String | Virtual card status, such as `ACTIVE`. | | `cardType` | String | Virtual card type. | | `initialAmount` | Float | Initial spend limit requested for the card. | | `usedAmount` | Float | Amount already spent on the card. | | `createdAt` | DateTime | Time when the card was created. | | `authorizationSetting` | Object | Authorization settings attached to the virtual card. |
### Example Requests Create a card for an authorized user with a saved address: ```curl theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation CreateVirtualCard($input: CreateVirtualCardInput!) { createVirtualCard(input: $input) { virtualCardId userId cardholderName virtualCardLast4 status cardType initialAmount usedAmount createdAt } }", "variables": { "input": { "idempotencyKey": "931e8841-cf23-4f36-8bf8-169384042ec2", "offerId": "09a9c8d1-9c4b-46fa-8a7a-508812a2a0d9", "authUserId": "8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d", "userAddressId": "1f6b2c3d-4e5f-4a67-9a10-2b3c4d5e6f70", "spendLimit": 100, "spendLimitDuration": "LIFETIME", "cardNickname": "Ada Travel Card", "primaryFundingSource": "FLUZ_BALANCE" } } }' ``` Create a card for an authorized user with a new inline billing address: ```curl theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation CreateVirtualCard($input: CreateVirtualCardInput!) { createVirtualCard(input: $input) { virtualCardId userId cardholderName virtualCardLast4 status cardType initialAmount usedAmount createdAt } }", "variables": { "input": { "idempotencyKey": "c20341f0-47e3-4d52-8361-1d7c37736b65", "offerId": "09a9c8d1-9c4b-46fa-8a7a-508812a2a0d9", "authUserId": "8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d", "spendLimit": 100, "billingAddress": { "streetAddressLine1": "456 Market St", "streetAddressLine2": "Suite 200", "country": "United States", "city": "San Francisco", "state": "CA", "postalCode": "94105" } } } }' ``` ```typescript theme={null} const response = await fetch('https://transactional-graph.staging.fluzapp.com/api/v1/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify({ query: ` mutation CreateVirtualCard( $input: CreateVirtualCardInput! ) { createVirtualCard(input: $input) { virtualCardId userId cardholderName virtualCardLast4 status cardType initialAmount usedAmount createdAt } } `, variables: { input: { idempotencyKey: '931e8841-cf23-4f36-8bf8-169384042ec2', offerId: '09a9c8d1-9c4b-46fa-8a7a-508812a2a0d9', authUserId: '8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d', userAddressId: '1f6b2c3d-4e5f-4a67-9a10-2b3c4d5e6f70', spendLimit: 100, spendLimitDuration: 'LIFETIME', cardNickname: 'Ada Travel Card', primaryFundingSource: 'FLUZ_BALANCE', }, }, }), }); const data = await response.json(); if (data.errors?.[0]?.extensions?.code === 'VC-0020') { const userAddressId = data.errors[0].extensions.addressId; console.log('Billing address pending issuer approval. Retry with userAddressId:', userAddressId); } else { console.log('Virtual card created:', data.data.createVirtualCard); } ``` ### Full Flow: Register User, Add Authorized User, Add Address, Create Card This flow registers a new Fluz user, adds that user to the caller's account as an authorized user, saves a billing address for that authorized cardholder, then creates a virtual card on their behalf. Step 1: Register the user. This requires a Bearer token for the developer user of an ACTIVE application with registration enabled. ```curl theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation RegisterUser($firstName: String!, $lastName: String!, $phoneNumber: String!, $regionCode: String!, $emailAddress: String!, $dateOfBirth: String!, $billingAddress: VirtualCardBillingAddressInput!, $acceptCardholderAgreement: Boolean!) { registerUser(firstName: $firstName, lastName: $lastName, phoneNumber: $phoneNumber, regionCode: $regionCode, emailAddress: $emailAddress, dateOfBirth: $dateOfBirth, billingAddress: $billingAddress, acceptCardholderAgreement: $acceptCardholderAgreement) { success error { code message } } }", "variables": { "firstName": "Ada", "lastName": "Lovelace", "phoneNumber": "5555555555", "regionCode": "US", "emailAddress": "ada.lovelace@example.com", "dateOfBirth": "1990-01-31", "billingAddress": { "streetAddressLine1": "1600 Amphitheatre Pkwy", "city": "Mountain View", "state": "CA", "postalCode": "94043", "country": "United States" }, "acceptCardholderAgreement": true } }' ``` Step 2: Add the registered user as an authorized user on the caller's account. ```curl theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation AddAuthorizedUser($email: String, $roles: [UACRoleType!]!) { addAuthorizedUser(email: $email, roles: $roles) { success authUserId roles status pendingActionId error { code message } } }", "variables": { "email": "ada.lovelace@example.com", "roles": ["SPENDER"] } }' ``` Continue only after the returned `status` is `ACTIVE`. If the status is `PENDING`, the authorized user must accept the invite before `addVirtualCardAddress` or `createVirtualCard` can use the returned `authUserId`. Step 3: Save the authorized user's billing address. ```curl theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation AddVirtualCardAddress($input: AddVirtualCardAddressInput!) { addVirtualCardAddress(input: $input) { userAddressId streetAddressLine1 streetAddressLine2 country city state postalCode } }", "variables": { "input": { "authUserId": "8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d", "billingAddress": { "streetAddressLine1": "456 Market St", "streetAddressLine2": "Suite 200", "country": "United States", "city": "San Francisco", "state": "CA", "postalCode": "94105" } } } }' ``` Step 4: Create the virtual card for the authorized user with the saved address. ```curl theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation CreateVirtualCard($input: CreateVirtualCardInput!) { createVirtualCard(input: $input) { virtualCardId userId cardholderName virtualCardLast4 status cardType initialAmount usedAmount createdAt } }", "variables": { "input": { "idempotencyKey": "931e8841-cf23-4f36-8bf8-169384042ec2", "offerId": "09a9c8d1-9c4b-46fa-8a7a-508812a2a0d9", "authUserId": "8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d", "userAddressId": "1f6b2c3d-4e5f-4a67-9a10-2b3c4d5e6f70", "spendLimit": 100, "spendLimitDuration": "LIFETIME", "cardNickname": "Ada Travel Card", "primaryFundingSource": "FLUZ_BALANCE" } } }' ``` ### Error Codes | Code | Message | Description | | :---------- | :----------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ARG-0001` | Invalid arguments received | Required input is missing or invalid, `spendLimit` is below `$5`, `lockDate` is not in the future, `offerId` is invalid, `authUserId` is invalid, `userAddressId` does not belong to the account/cardholder, or `BANK_ACCOUNT` funding was selected without `bankAccountId`. | | `AUTH-0008` | Invalid user access | The Bearer token could not be resolved to a caller. Verify the token is valid. | | `AUTH-0031` | The requested scopes must be granted by the user first. | The token is missing the `CREATE_VIRTUALCARD` scope required to create virtual cards. | | `AUTH-0034` | No authorized user found with this id on the caller's account. | The `authUserId` does not exist on the caller's account, is not ACTIVE, or refers to an OWNER assignment. | | `VC-0001` | Please try another payment method. If you continue experiencing issues, please contact our support team. | The card could not be created or the issuer rejected the billing address. | | `VC-0019` | We are unable to get the virtual card offer. If you continue experiencing issues, please contact our support team. | The requested virtual card offer could not be resolved. | | `VC-0020` | Virtual card billing address is pending approval. Please retry shortly using the returned addressId. | The billing address was submitted to the issuer but was not approved before the server-side wait window ended. Retry using `extensions.addressId` as `userAddressId`. |
# Decline Codes Source: https://docs.fluz.app/features/decline-codes The list of decline codes is consistent across our API | Code | Decline Category | Short Display Decline Reason | Display Decline Reason | Resolution | | ---------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---------- | | Validation | Access denied | You don't have permission to access this feature. Please contact support if you believe this is an error. | Contact support | | | Validation | Too many attempts | You've exceeded the number of allowed attempts. Please wait 15 minutes before trying again. | | | | Validation | Invalid information provided | Some of the information entered is incorrect. Please review your details and try again. | | | | Validation | Missing information | Some required information is missing. Please complete all required fields and try again. | | | | Validation | Address verification error | The address on your account couldn't be verified. Please verify that it is correct or contact support for assistance. | Contact support | | | Validation | Address not found | We couldn't locate the address you selected. Please verify the address is correct and try again. | Contact support | | | Validation | Name verification error | The first name on your account couldn't be verified. Please verify that it is correct or contact support for assistance. | Contact support | | | Validation | Name verification error | The last name on your account couldn't be verified. Please verify that it is correct or contact support for assistance. | Contact support | | | Validation | Birthdate verification error | Your date of birth couldn't be verified. Please verify that it is correct or contact support for assistance. | Contact support | | | Validation | Email verification error | Your email couldn't be verified. Please verify that it is correct or contact support for assistance. | Contact support | | | Validation | Phone number verification error | Your phone number couldn't be verified. Please verify that it is correct or contact support for assistance. | Contact support | | | Validation | Billing address missing | Please add a billing address to your payment method and try again. | | | | Account | Verification error | We couldn't verify your information to create your card. Please provide your SSN or contact support for assistance. | Complete verification | | | Internal | Internal error | A server error prevented us from completing this transaction. Please try again shortly. | | | | Internal | Transaction failed | We couldn't complete this transaction. Please try again later. Any pending charge will automatically be removed within 2-7 business days. | | | | Account | Balance unavailable | We couldn't load your balance information. Please try again or contact support if the problem continues. | Contact support | | | Account | Settings unavailable | We couldn't load your cash balance settings. Please try again or contact support if the issue persists. | Contact support | | | Acquiring | Bank relinking required | We couldn't verify your bank account details. Please relink your bank account. | Relink bank | | | Acquiring | Invalid billing address | The billing address doesn't match your bank records. Please update the billing address. | Update billing address | | | Acquiring | Bank relink required | Your linked bank account is no longer active. Please relink your bank account. | Relink bank | | | Acquiring | Unavailable funding source | The selected funding source isn't available for this transaction. Please select a different funding source. | Change funding source | | | Acquiring | Unavailable funding source | The selected funding source does not have enough spend power for this transaction. Please select a different funding source. | Change funding source | | | Acquiring | Backup funding source required | Change funding source | | | | Acquiring | Backup funding source not available | Your selected backup funding source isn't available for this transaction. Please select a different funding source or switch your backup. | Change funding source | | | Acquiring | Backup funding source expired | Your selected backup funding source has expired. Please select a different funding source or update your backup. | Change funding source | | | Acquiring | Unavailable funding source | The selected funding source isn't available for this transaction. Please select a different funding source. | Change funding source | | | Acquiring | Payment processing error | This transaction couldn’t be completed due to a temporary processing error. Please try again later. | | | | Acquiring | Duplicate Transaction | This appears to be a duplicate transaction and was blocked. No action is needed if the original transaction succeeded. | | | | Acquiring | Connection error | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Connection error | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Connection error | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Connection error | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Connection error | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Connection error | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Connection error | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Incorrect CVV | The CVV entered doesn’t match the card. Please check the CVV on your card and try again. | | | | Acquiring | Incorrect billing address | The billing address doesn't match your bank records. Please update the billing address. | Update billing address | | | Acquiring | Insufficient funds | There aren't enough funds available on this card. Please add funds to continue. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Invalid card number | The card number entered is incorrect. Please check the card number and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Expired card | This card has expired and can't be used. Please select a different funding source. | Change funding source | | | Acquiring | Issuer bank system error | This transaction couldn't be completed due to a temporary system issue on your bank's side. Please select a different funding source or try again later. | Change funding source | | | Acquiring | Unsupported transaction type | This transaction type isn't supported for this card. Please select a different funding source. | Change funding source | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | System unavailable | The bank network is temporarily unavailable. Please try again later. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Card details issue | There's an issue with the card details or status. Please update your card details | Update card details | | | Acquiring | Incorrect address | The address doesn't match the merchant records. Please update your address. | Update address | | | Acquiring | Invalid card number | The card number entered is incorrect. Please check the card number and try again. | | | | Acquiring | Temporary system issue | This transaction couldn’t be completed due to a temporary issue on our side. Please try again later. | | | | Acquiring | Temporary system issue | This transaction couldn’t be completed due to a temporary issue on our side. Please try again later. | | | | Acquiring | Temporary system issue | This transaction couldn’t be completed due to a temporary issue on our side. Please try again later. | | | | Acquiring | Temporary system issue | This transaction couldn’t be completed due to a temporary issue on our side. Please try again later. | | | | Acquiring | Temporary system issue | This transaction couldn’t be completed due to a temporary issue on our side. Please try again later. | | | | Acquiring | Temporary system issue | This transaction couldn’t be completed due to a temporary issue on our side. Please try again later. | | | | Acquiring | Incorrect billing address | The billing address doesn't match your bank records. Please update the billing address. | Update billing address | | | Acquiring | Transaction details invalid | Some details about your card or transaction are missing or incorrect. Please review and try again. | | | | Acquiring | Invalid card number | The card number entered is incorrect. Check the card number and try again. | | | | Acquiring | Temporary system issue | This transaction couldn’t be completed due to a temporary issue on our side. Please try again later. | | | | Acquiring | Unavailable category | This card can’t be used for this category right now. Please choose a different category or card. | | | | Acquiring | Unavailable category | This card can’t be used for this category right now. Please choose a different category or card. | | | | Acquiring | Card temporarily not supported | This card type isn't supported at the moment. Please select a different funding source. | Change funding source | | | Acquiring | Card not authorized | This card isn't authorized for use on your account. Please contact support to enable this card. | Contact support | | | Acquiring | Insufficient funds | There aren't enough funds available on this card. Please add funds or change funding source to continue. | Add money | | | Acquiring | Connection error | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Connection error | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Declined by your bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Connection error | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Declined by your bank | This transaction couldn’t be completed due to a temporary connection issue. Please check your connection and try again. | | | | Acquiring | Insufficient PayPal balance | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | Change funding source | | | Acquiring | Paypal account unlinked | Your PayPal account is no longer linked. Please relink your PayPal account.There aren't enough funds in your PayPal balance. Please select a different funding source. | Relink PayPal account | | | Acquiring | Paypal account closed | Your PayPal account can't be used for payments. Please select a different funding source. | Change funding source | | | Acquiring | Declined by bank | Your bank blocked this transaction for security reasons. Please confirm the transaction with your bank and try again. | | | | Acquiring | Unknown reason | This transaction was declined for an unknown reason. Please contact support for assistance. | Contact support | | | Gift Card | Unknown reason | This transaction was declined for an unknown reason. Please contact support for assistance. | Contact support | | | Gift Card | Merchant unavailable | This merchant is no longer available for purchases. Please choose a different merchant. | Choose merchant | | | Gift Card | Merchant unavailable | The merchant is temporarily unavailable. Please choose a different merchant or try again later. | Choose merchant | | | Gift Card | Limit exceeded | This transaction exceeds one of your limits. Would you like to update the card limit? | Update card limit | | | Gift Card | Merchant connection error | This transaction couldn't be completed due to a temporary connection issue with the merchant. Please choose a different merchant or try again later. | | | | Gift Card | Merchant connection error | This transaction couldn't be completed due to a temporary connection issue with the merchant. Please choose a different merchant or try again later. | | | | Gift Card | Merchant connection error | This transaction couldn't be completed due to a temporary connection issue with the merchant. Please choose a different merchant or try again later. | | | | Gift Card | Merchant connection error | This transaction couldn't be completed due to a temporary connection issue with the merchant. Please choose a different merchant or try again later. | | | | Gift Card | Merchant connection error | This transaction couldn't be completed due to a temporary connection issue with the merchant. Please choose a different merchant or try again later. | | | | Gift Card | Vendor insufficient funds | This purchase couldn’t be completed at this time. Please try again later. | | | | Gift Card | Unavailable amount | There's insufficient stock available from the merchant for this transaction. Please select a different amount or try again later. | Contact support | | | Gift Card | Merchant connection error | This transaction couldn't be completed due to a temporary connection issue with the merchant. Please choose a different merchant or try again later. | | | | Gift Card | Unknown reason | This transaction was declined for an unknown reason. Please contact support for assistance. | Contact support | | | Gift Card | Merchant connection error | This transaction couldn't be completed due to a temporary connection issue with the merchant. Please choose a different merchant or try again later. | | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Insufficient funds | There aren’t enough funds available on this card. Would you like to add funds? | Add money | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Insufficient funds | There aren’t enough funds available on this card. Would you like to add funds? | Add money | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Payouts | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Rewards | Unknown reason | This transaction was declined for an unknown reason. Please contact support for assistance. | Contact support | | | Rewards | No voucher available | There’s no active boost available on your account. | Add voucher | | | Risk & Verification Controls | Transaction error | Something went wrong with this transaction. Please try again or contact support. | Contact support | | | Risk & Verification Controls | Account paused | Your account is under review and temporarily restricted from payments. Please wait while the review is completed and try again. | Contact support | | | Risk & Verification Controls | Account under review | Your account is under review and temporarily restricted from payments. Please wait while the review is completed and try again. | | | | Risk & Verification Controls | Account paused | Your account is paused and can't be used for payments. Contact support to restore access. | Contact support | | | Risk & Verification Controls | Account under review | Your account is under review and temporarily restricted from payments. Please wait while the review is completed and try again. | | | | Risk & Verification Controls | Verification required | Verification is required before this transaction can be completed. Please complete verification to proceed. | Complete verification | | | Risk & Verification Controls | Account under review | Your account is under review and temporarily restricted from payments. Please wait while the review is completed and try again. | | | | Risk & Verification Controls | Feature access restricted | You do not have access to this feature on your account. Please contact support for assistance. | Contact support | | | Risk & Verification Controls | Verification required | Verification is required before this transaction can be completed. Please complete verification to proceed. | Complete verification | | | Risk & Verification Controls | Verification required | Verification is required before this transaction can be completed. Please complete verification to proceed. | Complete verification | | | Risk & Verification Controls | Verification required | Verification is required before this transaction can be completed. Please complete verification to proceed. | Complete verification | | | Risk & Verification Controls | Account under review | Your account is under review and temporarily restricted from payments. Please wait while the review is completed and try again. | | | | Risk & Verification Controls | Unusual activity | Unusual activity was detected on your account. Please confirm your activity to remove the restriction and try again. | | | | Risk & Verification Controls | Account paused | Your account is paused and can't be used for payments. Contact support to restore access. | Contact support | | | Risk & Verification Controls | Open/unpaid balance | There's an unpaid balance on your account. Please pay the outstanding balance to continue. | Pay balance | | | Risk & Verification Controls | Verification required | Verification is required before this transaction can be completed. Please complete verification to proceed. | Complete verification | | | Risk & Verification Controls | Account under review | Your account is under review and temporarily restricted from payments. Please wait while the review is completed and try again. | | | | Risk & Verification Controls | Account paused | Your account is paused and is restricted from payments. Please wait while the review is completed and try again. | Contact support | | | Risk & Verification Controls | Account paused | Your account is paused and is restricted from payments. Please wait while the review is completed and try again. | Contact support | | | Risk & Verification Controls | Verification required | Verification is required before this transaction can be completed. Please complete verification to proceed. | Complete verification | | | Risk & Verification Controls | Account under review | Your account is under review and temporarily restricted from payments. Please wait while the review is completed and try again. | | | | Risk & Verification Controls | Spend power too low | The amount exceeds your account's daily limit. Please try again later. | | | | Risk & Verification Controls | Spend power too low | The amount exceeds your account's daily limit. Please try again later. | | | | Risk & Verification Controls | Spend power too low | The amount exceeds your account's monthly limit. Please try again later. | | | | Risk & Verification Controls | Spend power too low | The amount exceeds your account's monthly limit. Please try again later. | | | | Risk & Verification Controls | Spend power too low | The amount exceeds your account's weekly limit. Please try again later. | | | | Risk & Verification Controls | Spend power too low | The amount exceeds your account's weekly limit. Please try again later. | | | | Risk & Verification Controls | Merchant limit reached | The amount exceeds this merchant's daily limit. Please try another merchant or try again later. | | | | Risk & Verification Controls | Merchant limit reached | The amount exceeds this merchant's daily limit. Please try another merchant or try again later. | | | | Risk & Verification Controls | Merchant limit reached | The amount exceeds your available spend power. Please select a different funding source. | | | | Risk & Verification Controls | Merchant limit reached | The amount exceeds this merchant's monthly limit. Please try another merchant or try again later. | | | | Risk & Verification Controls | Merchant limit reached | The amount exceeds this merchant's weekly limit. Please try another merchant or try again later. | | | | Risk & Verification Controls | Merchant limit reached | The amount exceeds this merchant's weekly limit. Please try another merchant or try again later. | | | | Risk & Verification Controls | Spend power too low | The amount exceeds your available spend power. Please complete verification to proceed. | Get verified | | | Risk & Verification Controls | Payment method not allowed | This payment method can't be used for this transaction. Please select a different funding source. | Change funding source | | | Risk & Verification Controls | Spend power too low | The amount exceeds your available spend power. Please select a different funding source. | Change funding source | | | Risk & Verification Controls | Feature access restricted | You do not have access to this feature on your account. Please contact support for assistance. | Contact support | | | Risk & Verification Controls | Incorrect PIN | The PIN entered doesn’t match the PIN set for this card. Please double-check your PIN and try again. | | | | Virtual Card | Verification required | Verification is required before this transaction can be completed. Would you like to complete verification? | Complete verification | | | Virtual Card | Account restricted | Your account has restrictions that prevent this transaction. Would you like to contact support for more information? | Contact support | | | Virtual Card | Incorrect details | Some of the card details entered are incorrect. Would you like to update the card details? | Update card details | | | Virtual Card | Incorrect details | Some of the card details entered are incorrect. Would you like to update the card details? | Update card details | | | Virtual Card | Incorrect details | Some of the card details entered are incorrect. Would you like to update the card details? | Update card details | | | Virtual Card | Incorrect details | Some of the card details entered are incorrect. Would you like to update the card details? | Update card details | | | Virtual Card | Incorrect details | Some of the card details entered are incorrect. Would you like to update the card details? | Update card details | | | Virtual Card | Incorrect details | Some of the card details entered are incorrect. Would you like to update the card details? | Update card details | | | Virtual Card | Insufficient program funds | There aren’t enough funds available in the program balance. Please try again later. | | | | Virtual Card | System error | This transaction couldn’t be completed due to a temporary system issue. Please try again later. | | | | Virtual Card | Blocked transaction | This transaction was blocked due to a restriction on this card. Would you like to contact support for more information? | Contact support | | | Virtual Card | Blocked transaction | This transaction was blocked due to a restriction on this card. Would you like to contact support for more information? | Contact support | | | Virtual Card | Blocked transaction | This transaction was blocked due to a restriction on this card. Would you like to contact support for more information? | Contact support | | | Virtual Card | Card locked | This card is locked and can’t be used for payments. Would you like to contact support to unlock the card? | Contact support | | | Virtual Card | Card locked | This card is locked and can’t be used for payments. Would you like to contact support to unlock the card? | Contact support | | | Virtual Card | Admin locked card | This card was locked by an administrator. Please contact your account admin to unlock the card. | | | | Virtual Card | Locked to another merchant | This card is locked for use with a different merchant and can’t be used here. Would you like to lock your card to a different merchant? | Change merchant | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Spend limit exceeded | This transaction would exceed the merchant limit on this card. Please try again later | | | | Virtual Card | Spend limit exceeded | This transaction would exceed the merchant limit on this card. Please try again later | | | | Virtual Card | Spend limit exceeded | This transaction would exceed the merchant limit on this card. Please try again later | | | | Virtual Card | Transaction limit exceeded | This transaction exceeds the allowed amount for this card. Would you like to update your card limits? | Update card limit | | | Virtual Card | Vendor limit exceeded | This transaction exceeds the vendor’s allowed limits. Please try again later | | | | Virtual Card | Spend limit exceeded | This transaction would exceed the merchant limit on this card. Please try again later | | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Card limit exceeded | This transaction would exceed your card’s lifetime limit. Would you like to update your card limit? | Update card limit | | | Virtual Card | Unknown reason | This transaction was declined for an unknown reason. Would you like to contact support? | Contact support | | | Virtual Card | System error | This transaction couldn’t be completed due to a temporary system issue. Please try again later. | | | | Virtual Card | Bank account unlinked | Your bank account is no longer linked. Would you like to relink your bank account? | Relink bank | | | Virtual Card | Bank account unresponsive | Your bank didn’t respond while processing the payment. Would you like to change your funding source? | Change funding source | | | Virtual Card | Insufficient funds | There aren’t enough funds available on this card. Would you like to add funds? | Add money | | | Virtual Card | Insufficient funds | There aren’t enough funds available on this card. Would you like to add funds? | Add money | | | Virtual Card | Insufficient funds | There aren’t enough funds available on this card. Would you like to add funds? | Add money | | | Virtual Card | Insufficient funds | There aren’t enough funds available on this card. Would you like to add funds? | Add money | | | Virtual Card | Unable to process | We can't complete this transaction right now due to your account status. Our support team can help you resolve this quickly. | | | | Virtual Card | Card already active | This user already has an active card on this account. You'll need to deactivate their existing card before issuing a new one. | | | | Virtual Card | Card profile not found | We couldn't locate the card profile needed to complete this request. Please contact support for assistance. | Contact support | | | Virtual Card | Account holder does not have an open application | Account holder does not have an open application. | Contact support | | | Virtual Card | Verification in progress | Your application is still being reviewed. We'll notify you once it's approved and you can start using your card. | | | | Virtual Card | Product not found | We're missing some account setup details. Please contact support to resolve this issue. | Contact support | | | Virtual Card | Card profile pending approval | This card profile hasn't been approved yet. Please wait for approval or contact support for more information. | Contact support | | | Virtual Card | Card product not found | We couldn't locate the card product needed for this transaciton. Please contact support for assistance. | Contact support | | | Virtual Card | Card product unavailable | This card product is either not found or no longer active. Please contact support to explore available card options. | Contact support | | | Virtual Card | Card already exists | A card has already been created for this transaction. | | | | Virtual Card | Duplicate card detected | A card with these details already exists in your account. Contact support if you need help managing your cards. | Contact support | | | Virtual Card | Card type not supported | Only Visa or Mastercard cards are accepted. Please use a different card to complete this transaction. | | | | Virtual Card | Not available in your country | This product isn't currently available in your country. Please contact support to learn about available options in your region. | Contact support | | | Virtual Card | Unable to create card | New cards can't be issued for this product right now. Contact support to explore your options. | Contact support | | | Virtual Card | Address missing | We need a recipient address to create this card. Please add a shipping address and try again. | | | | Virtual Card | Missing recipient details | We need the recipient's full name and shipping address to create this card. Please add these details and try again. | | | | Virtual Card | Cardholder account closed | We can't create a card for a closed account. Please contact support if you believe this is an error. | Contact support | | | Virtual Card | Verification code error | We couldn't generate the security code for your card. Please try again or contact support if the problem continues. | Contact support | | | Virtual Card | Unable to create card | We couldn't complete the security setup for your card. Please try again in a few moments or contact support for help. | Contact support | | | Virtual Card | Invalid BIN prefix | Only the approved BIN prefix is allowed for this operation. Please verify your card configuration. | Complete verification | | | Virtual Card | Inactive account | This account isn't currently active. Contact support to reactivate your account and resume card use. | Contact support | | | Virtual Card | Verification required | We need to verify your identity before you can use your card. Please complete the verification process or contact support for help. | Complete verification | | | Virtual Card | Conversion not available | Physical cards can't be converted to virtual cards when reissuing. Please request a new virtual card separately if needed. | | | | Virtual Card | Card can't be reissued | You can only reissue your latest active or suspended card. Please try again with the correct card. | | | | Virtual Card | Card can't be reissued | This card can't be reissued due to its current status. Contact support to discuss replacement options. | Contact support | | | Virtual Card | Can't activate this card | Only unactivated or suspended cards can be activated. This card is in a different status. Contact support for assistance. | Contact support | | | Virtual Card | Card expired | Your card is no longer valid. Contact support or create a new card. | Contact support | | | Virtual Card | Card must be active | This action requires an active card. Please try again or contact support for help. | Contact support | | | Virtual Card | Verification error | We couldn't verify your information to create your card. Please contact support for assistance. | Contact support | | | Virtual Card | Verification in progress | Your application for this product is still being reviewed. | Contact support | | | Virtual Card | Application declined | Your card application for this card program wasn't approved. | Contact support | | | Virtual Card | U.S. address required | This offer is only available to customers with a U.S. address. Please contact support if you believe this is an error. | Contact support | | | Virtual Card | Virtual card update failed | We couldn't update your virtual card settings. Please try again or contact support if the problem continues. | Contact support | | | Virtual Card | Virtual card setup issue | We couldn't access or create your virtual card settings. Please try again or contact support if the problem continues. | Contact support | | | Virtual Card | Agreement not found | We couldn't locate your approved virtual card agreement. Please contact support for assistance. | Contact support | | | Virtual Card | Invalid spend limit | Please enter a lower spend limit for this virtual card and try again. | | | | Virtual Card | Unable to create authorized user | There was a problem creating an authorized user for this virtual card. Please try again or contact support if the issue continues. | Contact support | | # Delete Bank Card Source: https://docs.fluz.app/features/delete-bank-card You can delete (deactivate) an existing bank card using the `deleteBankCard` mutation. This mutation requires the `MANAGE_PAYMENT` scope. The `deleteBankCard` [mutation](/api-reference/overview) sets the status of a bank card to `INACTIVE`. Once deleted, the card cannot be used for transactions. ### Sample request ```json theme={null} { "query": "mutation deleteBankCard($input: DeleteBankCardInput!) { deleteBankCard(input: $input) { bankCardId cardStatus }}", "variables": { "input": { "bankCardId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19" } } } ``` This mutation requires the `DeleteBankCardInput` input type. Any field marked with an exclamation mark (`!`) in the schema is mandatory and must be included in the request. | Field name | Type | Description | | :--------- | :---- | :-------------------------------------- | | bankCardId | UUID! | The unique identifier of the bank card. | ### DeleteBankCardInput ```Text JSON theme={null} { "bankCardId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19" } ``` ### Sample response ```json theme={null} { "data": { "deleteBankCard": { "bankCardId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "cardStatus": "INACTIVE" } } } ``` Deleting a bank card will deactivate it immediately. The card will no longer be available for any transactions or funding operations. # Deposit From External Accounts Source: https://docs.fluz.app/features/deposit-from-external-accounts depositCashBalance When you're ready to move money into your Fluz account, you can deposit funds into the following three balances: 1. **Cash balance**- A regular cash balance deposit. 2. **Gift card balance**- A non-withdrawable deposit for gift card balance. 3. **Reserve balance** - A deposit held in reserve balance. # Make a Deposit To deposit funds into one of your Fluz balances, use the `depositCashBalance` [mutation](/api-reference/mutations/deposit-cash-balance). This mutation accepts an input object type of `DepositCashBalanceInput`, which allows you to specify the following details: 1. **Amount:** **Required** - The amount you wish to deposit. 2. **idempotencyKey:** **Required** - A unique client-generated UUID to ensure a request is processed only once. 3. **Funding Source:** The payment method from which the deposit will be made (e.g., bank account, credit or debit card, PayPal). In order to get your funding source's ID, use the `getWallet` [query](/api-reference/queries/get-wallet). 1. **`bankAccountId`** - If you want to pay with a bank account, define the bank account ID in the `depositCashBalance` mutation. 2. **`bankCardId`** - If you want to pay with a bank card, define the bank card ID in the `depositCashBalance` mutation. 3. **`paypalVaultID`** - If you want to pay with a PayPal account, define the PayPal account ID in the `depositCashBalance` mutation. 4. **Deposit Destination:** The balance where you want to deposit your funds. The available options for this are defined in the `CashBalanceDepositType` enumerator: 1. **`CASH_BALANCE`** 2. **`GIFT_CARD_BALANCE`** 3. **`RESERVE_BALANCE`** 5. **Merchant Category Code:** Only applies for **GIFT\_CARD\_BALANCE**. A four-digit number that classifies a business by the type of products or services it offers. Use `getMccList` to retrieve a list of valid MCCs. 6. **`userCashBalanceId`** - If `CASH_BALANCE`is selected, you can specify the cash balance (spend account) to deposit your funds 7. *`memo`*\* - If you want to attach a note to this transaction, provide a free-text memo here. Max 255 characters. 8. **`transactionCategory`** - If you want to categorize this transaction, provide a category name. Categories are created automatically on first use and reused if the same name is passed again. 9. **`attachmentId`** - If you want to attach a file to this transaction, provide the ID returned by the upload endpoint. See [Add Expense Details](/features/add-expense-details). 📘 See [Add Expense Details](/features/add-expense-details) for full details on uploading attachments and working with memos and categories. ## Sample Request ```graphql graphql theme={null} mutation depositCashBalance($input: DepositCashBalanceInput!) { depositCashBalance(input: $input) { cashBalanceDeposits { ...CashBalanceDepositFragment } balances { ...UserBalancesFragment } } } ``` ## DepositCashBalanceInput ```json theme={null} { "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "amount": 987.65, "depositType": "GIFT_CARD_BALANCE", "merchantCategoryCode": 1234, "bankAccountId": "0285c162-fb2f-4c32-b076-29166471f570", "bankCardId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "paypalVaultId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "userCashBalanceId": "0347adef-2b69-45e6-8db0-5b5daddf45d2" } ``` ## Sample Response ```json theme={null} { "data": { "depositCashBalance": { "cashBalanceDeposits": [ { "cashBalanceDepositId": "5f5c5b5f-4c4b-4e4e-9e9e-4f4f4f4f4f4f", "depositDisplayId": "102370", "depositAmount": "100.00", "depositFee": "2.00", "bankAccountId": "b2c3d4e5-f6a7-890b-c12d-34ef5678gh90", "transactionDate": "2024-08-21T10:30:00Z", "clearedDate": "2024-08-22T12:00:00Z", "status": "COMPLETED", "expectedClearedDate": "2024-08-22T12:00:00Z", "cashBalanceDepositType": "CASH_BALANCE", "cashBalanceSettlements": [ { "cashBalanceSettlementId": "255f8245-02c7-4817-901e-15fe265f6968", "cashBalanceDepositId": "255f8245-02c7-4817-901e-15fe265f6968", "availabilityType": "INSTANT", "status": "AVAILABLE" } ] } ], "balances": { "cashBalance": { "availableBalance": "25.00", "totalBalance": "25.00", "pendingBalance": "25.00", "lifetimeBalance": "25.00", }, "rewardBalance": { "availableBalance": "25.00", "totalBalance": "25.00", "lifetimeBalance": "25.00", }, "giftCardCashBalance": { "availableBalance": "25.00", "totalBalance": "25.00", "pendingBalance": "25.00", "lifetimeBalance": "25.00", } } } } } ``` # Deposit Response Once you initiate a deposit with the `depositCashBalance` [mutation](/api-reference/mutations/deposit-cash-balance), you will get a response providing details about the deposit. | Field Name | Type | Description | | :------------------ | :-------------------- | :------------------------------------------------------------------------------------------------------- | | cashBalanceDeposits | \[CashBalanceDeposit] | List of cash balance deposits made as part of the mutation. | | balances | UserBalances | The current balances for the user, including cash balance, rewards balance, and Fluz prepayment balance. | The `CashBalanceDeposit` object will contain important information about the deposit you initiated. | Field Name | Type | Description | | :--------------------- | :------------------------ | :--------------------------------------------------------------------------------------------------- | | cashBalanceDepositId | UUID! | Unique identifier for the cash balance deposit. | | depositDisplayId | String! | Display identifier for the deposit, intended for user-facing purposes. | | depositAmount | String! | The amount of the deposit. | | depositFee | String | Fee associated with the deposit, if applicable. | | bankCardId | UUID | Identifier of the bank card used for the deposit. | | bankAccountId | UUID | Identifier of the bank account used for the deposit. | | paypalVaultId | UUID | Identifier of the PayPal vault used for the deposit. | | transactionDate | DateTime! | Date and time when the transaction was made. | | clearedDate | DateTime | Date and time when the deposit cleared, if applicable. | | status | CashBalanceDepositStatus! | Current status of the deposit. | | expectedClearedDate | DateTime! | Expected date and time for the deposit to clear. | | cashBalanceDepositType | CashBalanceDepositType! | Type of the cash balance deposit, including `CASH_BALANCE`,`GIFT_CARD_PREPAYMENT`,`RESERVE_BALANCE`. | | cashBalanceSettlements | \[CashBalanceSettlement] | Settlement time details for the deposit, including status and type. | > 📘 Please note > > Deposits may settle instantly or within 2-5 business days. To learn how settlement times are calculated, [read this article.](https://help.fluz.app/en/articles/5773275-instant-access)
# Edit Spend Accounts Source: https://docs.fluz.app/features/edit-spend-accounts # Overview Each cash balance account has it's own nickname so user could easily distinguish between them, if there are a few of them. # Edit User Cash Balance Account ## Sample request You can update a user cash balance account with the `updateUserCashBalance` mutation. This mutation allows you edit spend account's nickname to help organize your funds. The query takes a `UpdateUserCashBalanceInput` input object. ```json theme={null} mutation updateUserCashBalance($input: UpdateUserCashBalanceInput!) { updateUserCashBalance(input: $input) { userCashBalanceId totalCashBalance availableCashBalance lifetimeCashBalance nickname status createdAt } } ``` This mutation requires the `UpdateUserCashBalanceInput` input type. Any field marked with an exclamation mark (`!`) in the schema is mandatory and must be included in the request. | Field name | Type | Description | | :---------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | | userCashBalanceId | UUID! | An unique identifier for the cash balance account. | | nickname | String! | A custom name for the cash balance account. This helps identify the purpose of the account. Has to be at least 2 characters long, but no longer than 100. | ## UpdateUserCashBalanceInput ```json theme={null} { "userCashBalanceId": "f8a3c9e1-7b2d-4f5e-9c8a-1d2e3f4g5h6i", "nickname": "New Team Travel" } ``` ## Sample response The response from the `updateUserCashBalance` mutation will include the newly created cash balance account details, including its unique identifier and initial balance information. ```json theme={null} { "data": { "updateUserCashBalance": { "userCashBalanceId": "f8a3c9e1-7b2d-4f5e-9c8a-1d2e3f4g5h6i", "totalCashBalance": "0", "availableCashBalance": "0", "lifetimeCashBalance": "0", "nickname": "New Team Travel", "status": "ACTIVE", "createdAt": "2025-01-15T10:30:00.000Z" } } } ``` ### Response Fields | Field name | Type | Description | | :------------------- | :--------------------- | :----------------------------------------------------------- | | userCashBalanceId | UUID! | Unique identifier for the cash balance account | | totalCashBalance | String! | Total cash balance in the account (starts at 0) | | availableCashBalance | String! | Available cash balance for immediate use (starts at 0) | | lifetimeCashBalance | String! | Cumulative total of all funds ever deposited (starts at 0) | | nickname | String | The custom name assigned to the account | | status | UserCashBalanceStatus! | Current status of the account (ACTIVE, CLOSED, or SUSPENDED) | | createdAt | DateTime! | Timestamp when the account was created | > ❗️ Authorization required > > This mutation requires the `MANAGE_PAYMENT` scope. Ensure your access token has been granted this scope before attempting to create a cash balance account. # Edit Card Overview Source: https://docs.fluz.app/features/edit-virtual-card The `editVirtualCard` mutation allows you to update various details of an existing virtual card, such as spend limits, lock date, and nickname. **Prerequisites:** a user access token with the `EDIT_VIRTUALCARD` scope, and the `virtualCardId` of the card to update. ## Arguments * **`input`** (`EditVirtualCardInput!`): The input object containing the virtual card ID and the fields to update. ## EditVirtualCardInput fields | Field | Type | Description | Required | | :--------------------- | :------------------------------ | :-------------------------------------------------------- | :------- | | `virtualCardId` | `UUID!` | The virtual card to update. | Yes | | `spendLimit` | `Float` | The maximum amount that you can charge to the card. | No | | `spendLimitDuration` | `VirtualCardSpendLimitDuration` | Card limit duration type. | No | | `lockDate` | `String` | The date when the card will be locked. Format: yyyy-mm-dd | No | | `lockCardNextUse` | `Boolean` | The setting to lock the card after next use. | No | | `cardNickname` | `String` | The card's nickname. | No | | `primaryFundingSource` | `VirtualCardFundingSource` | Primary Funding Source for Virtual Card. | No | | `bankAccountId` | `UUID` | The unique identifier for the bank account. | No | | `userCashBalanceId` | `UUID` | The unique identifier for the user cash balance. | No | ## cURL example ```curl theme={null} curl -X POST \ https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \ -d '{ "query": "mutation { editVirtualCard( input: { virtualCardId: \"07df5653-43a8-4532-9881-3ab5857bbe11\", spendLimit: 200.00, spendLimitDuration: MONTHLY, cardNickname: \"Edited Card Nickname\", lockCardNextUse: false } ) { virtualCardId cardholderName expiryMonth expiryYear virtualCardLast4 status cardType initialAmount usedAmount createdAt authorizationSetting { virtualCardAsaSettingsId lockDate dailySpendLimit monthlySpendLimit cardNickname lockCardNextUse } } }" }' ``` ## Sample response ```json theme={null} { "data": { "editVirtualCard": { "virtualCardId": "07df5653-43a8-4532-9881-3ab5857bbe11", "userId": "00a2ec0a-255f-4fe7-85ad-2958dc8d3c72", "cardholderName": "John Doe", "expiryMonth": "12", "expiryYear": "27", "virtualCardLast4": "7890", "status": "ACTIVE", "cardType": "MULTI_USE", "initialAmount": 200, "usedAmount": 0, "createdAt": "2023-10-26T10:00:00Z", "authorizationSetting": { "virtualCardAsaSettingsId": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "lockDate": "1875743999999", "dailySpendLimit": null, "monthlySpendLimit": "200.00", "cardNickname": "Edited Card Nickname", "lockCardNextUse": false } } } } ``` ## Response fields | Field | Type | Description | | ----------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------- | | `virtualCardId` | `UUID` | The unique identifier of the virtual card that was edited. | | `cardholderName` | `String` | The name of the cardholder associated with the virtual card. | | `expiryMonth` | `String` | The expiration month of the virtual card. | | `expiryYear` | `String` | The expiration year of the virtual card. | | `virtualCardLast4` | `String` | The last four digits of the virtual card number. | | `status` | `VirtualCardStatus` | The current status of the virtual card. | | `cardType` | `VirtualCardType` | The type of the virtual card (e.g., `MULTI_USE`). | | `initialAmount` | `Float` | The initial amount (spend limit) set on the card when it was created. | | `usedAmount` | `Float` | The total amount spent on the virtual card. | | `createdAt` | `DateTime` | The timestamp when the virtual card was originally created. | | `authorizationSetting` | `VirtualCardAuthorizationSetting` | An object containing the authorization settings for the virtual card, reflecting any updates. | | `authorizationSetting.virtualCardAsaSettingsId` | `UUID` | The unique identifier for this specific authorization setting. | | `authorizationSetting.lockDate` | `String` | The date the card will be locked, if set. | | `authorizationSetting.dailySpendLimit` | `String` | The daily spending limit, if applicable. | | `authorizationSetting.monthlySpendLimit` | `String` | The monthly spending limit, if applicable. | | `authorizationSetting.cardNickname` | `String` | The updated nickname for the card. | | `authorizationSetting.lockCardNextUse` | `Boolean` | Indicates if the card will be locked after its next use. | ## Next steps Temporarily block all spend on a card without closing it. Re-enable a locked card so authorizations succeed again. # Manage linked funding sources Source: https://docs.fluz.app/features/funding-sources A **funding source** (also called a payment method) is how users pay for transactions on Fluz — gift card purchases, virtual card top-ups, wallet deposits, and more. Before any transaction can be completed, a user must have at least one funding source on file. A **bank card must always be added as a backup payment method**, even when the user intends to pay with their bank account or another method. *** ## Supported Funding Sources Fluz supports four funding source types: | Type | Add via API | Add via App / Web | Fees | Best Cashback | | ------------------------------------ | ----------- | ----------------- | ------------------------------- | -------------- | | **Bank Account (ACH)** | ✗ | ✓ | None | ✓ Full rate | | **Bank Card** (debit/credit/prepaid) | ✓ | ✓ | Debit: –1% · Credit/PayPal: –3% | Debit > Credit | | **PayPal / Apple Pay / Google Pay** | ✗ | ✓ | –3% | Lower | | **Fluz Balance** | — | — | None | Standard | > 💡 **Bank accounts carry no processing fees and earn the highest cashback rate.** Debit cards are the next best option. Credit cards, PayPal, Apple Pay, and Google Pay all carry a processing fee that reduces the effective cashback rate. *** ## Cashback Rate by Funding Source Using a **4% merchant cashback rate** as an example on a \$200 gift card purchase: ```text theme={null} ACH / Bank Account → 4.0% → $8.00 Debit Card → 3.0% → $6.00 (–1% processing fee) Credit Card → 1.0% → $2.00 (–3% processing fee) PayPal / Apple Pay → 1.0% → $2.00 (–3% processing fee) ``` *** ## What Can Be Done via API vs. App / Web ```mermaid theme={null} flowchart LR subgraph API["🔌 API"] A1[Add Bank Card\naddBankCard mutation] A2[Update Bank Card\nnickname / MCC] A3[Delete Bank Card] A4[Read all funding sources\ngetWallet query] end subgraph Web["🌐 App / Web Portal Only"] W1[Add Bank Account\nvia Plaid Link] W2[Remove Bank Account] W3[Add PayPal] W4[Add Apple Pay / Google Pay] W5[Set primary / preferred /\nbackup payment method] end ``` > 🚧 **Bank cards are the only funding source addable via the API.** All other funding source management — bank accounts, PayPal, Apple Pay, Google Pay, and payment method preferences — must be done through the Fluz app or web portal. *** ## Bank Account Bank accounts are linked via **Plaid**, and settle via ACH. They carry no fees and earn the full merchant cashback rate. **Added via: App or web portal only.** ![Adding a bank account on the Fluz app](https://downloads.intercomcdn.com/i/o/836799999/6f3ab32e045b7011dd3bd0fb/How+to+manage+and+select+a+funding+source.gif) *To add: Menu → Funding Sources → Add New → Bank Account → follow Plaid prompts.* > 💡 All accounts under a linked bank login are connected, including savings accounts. Users should remove savings accounts after linking — most banks do not permit payments from savings. *** ## Bank Card Bank cards (debit, credit, or prepaid) can be added via the **API** or through the app and web portal. **Added via:** `addBankCard `**mutation (API) or app / web portal.** ![Adding a bank card on the Fluz app](https://downloads.intercomcdn.com/i/o/836809654/93aa3bc1b082cff44ef2a70c/How+to+manage+and+select+a+funding+source.gif) *To add on web: Menu → Accounts and Cards → Bank Cards → Add New → enter card details and billing address.* Bank cards support the following operations through the API: * **Add** — `addBankCard` (requires `MANAGE_PAYMENT` scope) * **Update nickname** — `updateBankCardNickname` * **Update preferred MCC** — `updateBankCardPreferredMerchantCategoryCode` * **Delete** — `deleteBankCard` (sets status to `INACTIVE`) > ❗️ **A bank card must be added as a backup payment method before any transaction can complete.** When a user pays via ACH, a temporary hold may be placed on the backup card for the transaction amount. If the ACH payment clears, the hold releases within 1–7 business days. If it does not clear, the backup card is charged instead. *** ## PayPal / Apple Pay / Google Pay Digital wallets can be added through the app or web portal only. They carry a –3% processing fee applied to the effective cashback rate — the same as credit cards. **Added via: App or web portal only. Not available via API.** > 💡 Some merchants restrict which payment methods they accept. A merchant may accept ACH and debit only, and block credit cards and digital wallets. This is reflected in the `blockedPaymentTypes` field returned by `getWallet`. *** ## Reading Funding Sources via API The `getWallet` query returns all funding sources linked to a user's account, their current statuses, and any blocked payment types. ```graphql theme={null} query getWallet { getWallet { bankCards { bankCardId cardType lastFourDigits cardStatus } bankAccounts { bankAccountId type status lastFour } paypalAccounts { paypalVaultId status email } blockedPaymentTypes balances { rewardsBalance { availableBalance } cashBalance { availableBalance } } } } ``` See [View Funding Sources](/features/view-funding-sources) for the full field reference.
# Get All Transactions Source: https://docs.fluz.app/features/get-all-transactions ## Overview The Transactions API allows you to retrieve a comprehensive history of all financial transactions associated with your account. This includes purchases, deposits, withdrawals, transfers, bill payments, and all other financial activities. **Endpoint Type**: GraphQL Query\ **Authentication**: Required (JWT Bearer Token)\ **Required Scopes**: `LIST_PAYMENT` AND `LIST_PURCHASES`\ **Rate Limit**: Standard GraphQL rate limits apply *** ## Quick Start ### Basic Query ```graphql theme={null} query GetTransactions { getTransactions { transactions { recordId transactionType amount status channel connectedAppId connectedAppName createdAt } totalCount hasNextPage } } ```
*** ## Query Structure ```graphql theme={null} getTransactions( filter: TransactionFilterInput paginate: OffsetInput ): TransactionConnection! ``` ### Parameters | Parameter | Type | Required | Description | | ---------- | ------------------------ | -------- | --------------------------------------------------- | | `filter` | `TransactionFilterInput` | No | Filtering criteria for transactions | | `paginate` | `OffsetInput` | No | Pagination parameters (default: limit=20, offset=0) | *** ## Response Structure ### TransactionConnection ```graphql theme={null} type TransactionConnection { transactions: [Transaction] totalCount: Int! hasNextPage: Boolean! } ``` | Field | Type | Description | | -------------- | --------------- | ------------------------------------------------------------ | | `transactions` | `[Transaction]` | Array of transaction records | | `totalCount` | `Int!` | Total count of transactions matching filter (for pagination) | | `hasNextPage` | `Boolean!` | Whether more results are available | *** ## Filter Options ### TransactionFilterInput ```graphql theme={null} input TransactionFilterInput { # Record & Status recordId: [UUID] status: [TransactionStatus] # Amount Filters amount: Float amountGte: Float amountLte: Float finalAmount: Float finalAmountGte: Float finalAmountLte: Float # Cashback Filters cashbackAmount: Float cashbackAmountGte: Float cashbackAmountLte: Float cashbackPercentage: Float cashbackPercentageGte: Float cashbackPercentageLte: Float # Fee Filters feeAmount: Float feeAmountGte: Float feeAmountLte: Float # Date Filters createdGte: DateTime createdLte: DateTime updatedGte: DateTime updatedLte: DateTime # Merchant Filters merchantId: [UUID] merchant: [String] # Transaction Properties transactionType: [String] channel: [String!] category: [String] # Virtual Card Filters virtualCardProgram: [String] virtualCard: [UUID] # Other fundingSource: [String] userCashBalanceId: [UUID] referenceId: String liabilityId: UUID } ``` ### Filter Field Details #### Record & Status Filters ##### `recordId` **Type**: `[UUID]`\ **Description**: Filter by specific transaction record IDs. **Example**: ```graphql theme={null} filter: { recordId: ["550e8400-e29b-41d4-a716-446655440000"] } ``` ##### `status` **Type**: `[TransactionStatus]`\ **Description**: Filter by transaction status. **Options**: * `PENDING` - Transaction is being processed * `SETTLED` - Transaction completed successfully **Example**: ```graphql theme={null} filter: { status: [SETTLED] } ``` *** #### Amount Filters ##### `amount`, `amountGte`, `amountLte` **Type**: `Float`\ **Description**: Filter by exact amount or amount range in USD. * `amount` - Exact amount match * `amountGte` - Minimum amount (greater than or equal) * `amountLte` - Maximum amount (less than or equal) **Example**: ```graphql theme={null} # Transactions between $10 and $500 filter: { amountGte: 10.00 amountLte: 500.00 } ``` ##### `finalAmount`, `finalAmountGte`, `finalAmountLte` **Type**: `Float`\ **Description**: Filter by final amount (amount + fees). **Example**: ```graphql theme={null} filter: { finalAmountGte: 25.00 } ``` *** #### Cashback Filters ##### `cashbackAmount`, `cashbackAmountGte`, `cashbackAmountLte` **Type**: `Float`\ **Description**: Filter by cashback amount earned. **Example**: ```graphql theme={null} # Transactions that earned $5 or more in cashback filter: { cashbackAmountGte: 5.00 } ``` ##### `cashbackPercentage`, `cashbackPercentageGte`, `cashbackPercentageLte` **Type**: `Float`\ **Description**: Filter by cashback rate percentage. **Example**: ```graphql theme={null} # Transactions with 5% or higher cashback filter: { cashbackPercentageGte: 5.0 } ``` *** #### Fee Filters ##### `feeAmount`, `feeAmountGte`, `feeAmountLte` **Type**: `Float`\ **Description**: Filter by transaction fee amount. **Example**: ```graphql theme={null} # Transactions with fees filter: { feeAmountGte: 0.01 } ``` *** #### Date Filters ##### `createdGte`, `createdLte` **Type**: `DateTime`\ **Format**: ISO 8601 (e.g., `2025-01-01T00:00:00Z`)\ **Description**: Filter by transaction creation date range. **Example**: ```graphql theme={null} filter: { createdGte: "2025-01-01T00:00:00Z" createdLte: "2025-01-31T23:59:59Z" } ``` ##### `updatedGte`, `updatedLte` **Type**: `DateTime`\ **Description**: Filter by transaction last update date range. *** #### Merchant Filters ##### `merchantId` **Type**: `[UUID]`\ **Description**: Filter by specific merchant IDs. **Example**: ```graphql theme={null} filter: { merchantId: ["550e8400-e29b-41d4-a716-446655440000"] } ``` ##### `merchant` **Type**: `[String]`\ **Description**: Filter by merchant names (matches against destination field). **Example**: ```graphql theme={null} filter: { merchant: ["Amazon", "Walmart"] } ``` *** #### Transaction Properties ##### `transactionType` **Type**: `[String]`\ **Description**: Filter by specific transaction types. **Common Values**: * `Add Money` - Deposits * `Gift Card Purchase` - Gift card purchases * `Transfer - In` - Incoming transfers * `Transfer - Out` - Outgoing transfers * `Virtual Card Purchase` - Virtual card purchases * `Withdrawal`
**Example**: ```graphql theme={null} filter: { transactionType: ["Gift Card Purchase", "Add Money"] } ``` ##### `channel` **Type**: `[String!]`\ **Description**: Filter by platform channel. **Common Values**: * `WEB` - Web browser * `MOBILE` - Mobile app * `API` - API requests **Example**: ```graphql theme={null} filter: { channel: ["WEB", "MOBILE"] } ``` ##### `category` **Type**: `[String]`\ **Description**: Filter by transaction category. **Example**: ```graphql theme={null} filter: { category: ["Shopping", "Travel"] } ``` *** #### Virtual Card Filters ##### `virtualCardProgram` **Type**: `[String]`\ **Description**: Filter by virtual card program/issuer. **Example**: ```graphql theme={null} filter: { virtualCardProgram: ["TRANSPECOS", "SUTTON"] } ``` ##### `virtualCard` **Type**: `[UUID]`\ **Description**: Filter by specific virtual card IDs. **Example**: ```graphql theme={null} filter: { virtualCard: ["vc-1", "vc-2"] } ``` *** #### Other Filters ##### `fundingSource` **Type**: `[String]`\ **Description**: Search for funding source names (partial match on source or destination). **Example**: ```graphql theme={null} filter: { fundingSource: ["Visa"] } ``` ##### `referenceId` **Type**: `String`\ **Description**: Filter by external reference ID (e.g., purchase display ID). **Example**: ```graphql theme={null} filter: { referenceId: "1000123" } ``` ##### `liabilityId` **Type**: `UUID`\ **Description**: Filter by liability ID (for bill payments). *** ## Pagination ### OffsetInput ```graphql theme={null} input OffsetInput { limit: Int = 20 offset: Int = 0 } ``` | Field | Type | Default | Max | Description | | -------- | ---- | ------- | --- | ----------------------------------------- | | `limit` | Int | 20 | 20 | Number of transactions to return per page | | `offset` | Int | 0 | - | Number of transactions to skip | **Example - Page 1**: ```graphql theme={null} paginate: { limit: 20 offset: 0 } ``` **Example - Page 2**: ```graphql theme={null} paginate: { limit: 20 offset: 20 } ``` **Example - Check if more pages exist**: ```graphql theme={null} query GetTransactions { getTransactions(paginate: { limit: 20, offset: 0 }) { transactions { recordId } hasNextPage # Use this to determine if there are more results totalCount # Total matching records } } ``` *** ## Transaction Type ```graphql theme={null} type Transaction { recordId: UUID user: String! accountId: UUID! userId: UUID transactionType: String amount: Float! destination: String source: String externalFundingSourceActivity: Float fluzBalanceActivity: Float fee: Float cashback: Float # Ending balances after transaction giftCardPrepaymentBalanceAvailableBalance: Float giftCardPrepaymentBalanceTotalBalance: Float cashBalanceAvailableBalance: Float cashBalanceTotalBalance: Float seatBalanceAvailableBalance: Float seatBalanceTotalBalance: Float reserveBalanceAvailableBalance: Float reserveBalanceTotalBalance: Float otherCashBalanceAvailableBalance: Float otherCashBalanceTotalBalance: Float status: TransactionStatus referenceId: String description: String note: String category: String cardLastFour: String cardDisplayName: String originalCurrencyAmount: Float originalCurrencyCode: String conversionRate: Float merchantId: UUID descriptorId: UUID virtualCardProgram: String cashbackRate: Float bonusCashbackRate: Float channel: String sourceType: String logoUrl: String platformInstitutionLogo: String challengeLogoUrl: String invitedAccountId: UUID expectedClearedDate: DateTime liabilityId: UUID isGiftCardBalanceAffected: Boolean isCashBalanceAffected: Boolean isSeatBalanceAffected: Boolean isReserveBalanceAffected: Boolean transferId: UUID usedUserCashBalanceId: UUID connectedAppId: UUID connectedAppName: String memo: String transactionCategory: String attachmentUrl: String createdAt: DateTime updatedAt: DateTime } ``` ### Field Descriptions #### Core Transaction Fields | Field | Type | Description | | ----------------- | ----------------- | ---------------------------------------------------------- | | `recordId` | UUID | Unique identifier for this transaction record | | `user` | String | Display name of the user associated with transaction | | `accountId` | UUID | Account that owns this transaction | | `userId` | UUID | User who initiated the transaction | | `transactionType` | String | Type of transaction (PURCHASE, DEPOSIT, etc.) | | `amount` | Float | Primary transaction amount in USD | | `source` | String | Where the funds came from (e.g., "Bank Card \*\*\*\*1234") | | `destination` | String | Where the funds went (e.g., merchant name) | | `status` | TransactionStatus | Current status (PENDING or SETTLED) | #### Financial Details | Field | Type | Description | | ------------------------------- | ----- | -------------------------------------------------------- | | `externalFundingSourceActivity` | Float | Change to external funding sources (bank cards/accounts) | | `fluzBalanceActivity` | Float | Change to Fluz internal balances | | `fee` | Float | Fees charged for this transaction | | `cashback` | Float | Cashback earned from this transaction | | `cashbackRate` | Float | Cashback rate percentage | | `bonusCashbackRate` | Float | Additional bonus cashback rate | #### Balance Snapshots **Important**: All balance fields show the balance **after** this transaction was applied. | Field | Description | | ------------------------------------------- | ---------------------------------------------- | | `cashBalanceAvailableBalance` | Available cash balance after transaction | | `cashBalanceTotalBalance` | Total cash balance after transaction | | `seatBalanceAvailableBalance` | Available rewards balance after transaction | | `seatBalanceTotalBalance` | Total rewards balance after transaction | | `giftCardPrepaymentBalanceAvailableBalance` | Available prepayment balance after transaction | | `giftCardPrepaymentBalanceTotalBalance` | Total prepayment balance after transaction | | `reserveBalanceAvailableBalance` | Available reserve balance after transaction | | `reserveBalanceTotalBalance` | Total reserve balance after transaction | | `otherCashBalanceAvailableBalance` | Other cash available balance after transaction | | `otherCashBalanceTotalBalance` | Other cash total balance after transaction | #### Transaction Details | Field | Type | Description | | :-------------------- | :----- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `referenceId` | String | External reference (e.g., purchase display ID) | | `description` | String | Human-readable description | | `note` | String | Additional notes | | `category` | String | Transaction category | | `cardLastFour` | String | Last 4 digits of card used (if applicable) | | `cardDisplayName` | String | Display name of payment method | | `memo` | String | A free-text memo attached to this transaction. Set via [`updateTransactionMetadata`](/features/add-expense-details) or at transaction time on deposits, purchases, and transfers. | | `transactionCategory` | String | Category label attached to this transaction. Set via [`updateTransactionMetadata`](/features/add-expense-details) or at transaction time on deposits, purchases, and transfers. | | `attachmentUrl` | String | A signed URL for the file attached to this transaction. Set via [`updateTransactionMetadata`](/features/add-expense-details) or at transaction time. **This URL expires — do not store it.** Re-fetch the transaction when you need to access the file. | #### Merchant Information | Field | Type | Description | | -------------- | ------ | --------------------------- | | `merchantId` | UUID | Merchant identifier | | `descriptorId` | UUID | Transaction descriptor ID | | `logoUrl` | String | URL to merchant/source logo | #### Virtual Card Information | Field | Type | Description | | -------------------- | ------ | -------------------------------------------- | | `virtualCardProgram` | String | Virtual card program (LITHIC, MARQETA, etc.) | #### Currency Conversion | Field | Type | Description | | ------------------------ | ------ | ------------------------------------------------------ | | `originalCurrencyAmount` | Float | Amount in original currency (for foreign transactions) | | `originalCurrencyCode` | String | Original currency code (e.g., "EUR") | | `conversionRate` | Float | Exchange rate applied | #### Metadata | Field | Type | Description | | --------------------------- | -------- | ---------------------------------------- | | `channel` | String | Platform channel (WEB, MOBILE, API) | | `sourceType` | String | Type of funding source | | `liabilityId` | UUID | Associated liability (for bill payments) | | `transferId` | UUID | Associated transfer (for P2P) | | `usedUserCashBalanceId` | UUID | Specific cash balance used | | `isGiftCardBalanceAffected` | Boolean | Whether gift card balance changed | | `isCashBalanceAffected` | Boolean | Whether cash balance changed | | `isSeatBalanceAffected` | Boolean | Whether rewards balance changed | | `isReserveBalanceAffected` | Boolean | Whether reserve balance changed | | `connectedAppId` | UUID | Associated application ID | | `connectedAppName` | String | Associated application name | | `createdAt` | DateTime | When transaction was created | | `updatedAt` | DateTime | When transaction was last updated | *** ## Examples ### Example 1: Basic Transaction List **Query**: ```graphql theme={null} query GetRecentTransactions { getTransactions( paginate: { limit: 10, offset: 0 } ) { transactions { recordId transactionType amount description status cashback createdAt } totalCount hasNextPage } } ``` **Response**: ```json theme={null} { "data": { "getTransactions": { "transactions": [ { "recordId": "550e8400-e29b-41d4-a716-446655440000", "transactionType": "GIFT_CARD_PURCHASE", "amount": 50.00, "description": "Gift card purchase at Amazon", "status": "SETTLED", "cashback": 2.50, "createdAt": "2025-01-15T14:30:00Z" }, { "recordId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "transactionType": "DEPOSIT", "amount": 100.00, "description": "Cash Balance Deposit", "status": "SETTLED", "cashback": 0.00, "createdAt": "2025-01-14T10:15:00Z" } ], "totalCount": 2, "hasNextPage": false } } } ``` *** ### Example 2: Filtered by Date Range **Query**: ```graphql theme={null} query GetJanuaryTransactions { getTransactions( filter: { createdGte: "2025-01-01T00:00:00Z" createdLte: "2025-01-31T23:59:59Z" } paginate: { limit: 20, offset: 0 } ) { transactions { recordId transactionType amount description status createdAt } totalCount hasNextPage } } ``` *** ### Example 3: Purchases Only with Balances **Query**: ```graphql theme={null} query GetPurchaseHistory { getTransactions( filter: { transactionType: ["GIFT_CARD_PURCHASE"] status: [SETTLED] } paginate: { limit: 20, offset: 0 } ) { transactions { recordId referenceId amount description source destination cashback cashbackRate cashBalanceAvailableBalance seatBalanceAvailableBalance createdAt } totalCount hasNextPage } } ``` **Response**: ```json theme={null} { "data": { "getTransactions": { "transactions": [ { "recordId": "550e8400-e29b-41d4-a716-446655440000", "referenceId": "1000123", "amount": 50.00, "description": "Gift card purchase at Amazon", "source": "Visa ****1234", "destination": "Amazon", "cashback": 2.50, "cashbackRate": 5.0, "cashBalanceAvailableBalance": 102.50, "seatBalanceAvailableBalance": 25.00, "createdAt": "2025-01-15T14:30:00Z" } ], "totalCount": 1, "hasNextPage": false } } } ``` *** ### Example 4: High-Cashback Transactions **Query**: ```graphql theme={null} query GetHighCashbackTransactions { getTransactions( filter: { cashbackPercentageGte: 5.0 status: [SETTLED] } paginate: { limit: 10, offset: 0 } ) { transactions { recordId transactionType amount description cashback cashbackRate status createdAt } totalCount } } ``` *** ### Example 5: Amount Range Filter **Query**: ```graphql theme={null} query GetMediumTransactions { getTransactions( filter: { amountGte: 50.00 amountLte: 200.00 status: [SETTLED] } paginate: { limit: 20, offset: 0 } ) { transactions { recordId amount transactionType description createdAt } totalCount hasNextPage } } ``` *** ### Example 6: Virtual Card Transactions **Query**: ```graphql theme={null} query GetVirtualCardTransactions { getTransactions( filter: { virtualCardProgram: ["LITHIC", "MARQETA"] } paginate: { limit: 20, offset: 0 } ) { transactions { recordId amount description virtualCardProgram merchantId status createdAt } totalCount } } ``` *** ### Example 7: Pagination Example **Query - Get first page and check for more**: ```graphql theme={null} query GetFirstPage { getTransactions(paginate: { limit: 20, offset: 0 }) { transactions { recordId amount transactionType } totalCount hasNextPage } } ``` **Response shows hasNextPage=true**: ```json theme={null} { "data": { "getTransactions": { "transactions": [...], "totalCount": 150, "hasNextPage": true } } } ``` **Query - Get second page**: ```graphql theme={null} query GetSecondPage { getTransactions(paginate: { limit: 20, offset: 20 }) { transactions { recordId amount transactionType } totalCount hasNextPage } } ``` *** ## Error Handling ### Common Errors #### Missing or Invalid Token ```json theme={null} { "errors": [ { "message": "Authentication required", "extensions": { "code": "UNAUTHENTICATED" } } ] } ``` **HTTP Status**: 401 Unauthorized *** #### Insufficient Permissions ```json theme={null} { "errors": [ { "message": "Insufficient permissions: LIST_PAYMENT and LIST_PURCHASES scopes required", "extensions": { "code": "FORBIDDEN", "requiredScopes": ["LIST_PAYMENT", "LIST_PURCHASES"] } } ] } ``` **HTTP Status**: 403 Forbidden *** #### Invalid Filter Parameters ```json theme={null} { "errors": [ { "message": "Invalid date format for createdGte", "extensions": { "code": "BAD_USER_INPUT" } } ] } ``` **HTTP Status**: 400 Bad Request *** #### Rate Limit Exceeded ```json theme={null} { "errors": [ { "message": "Rate limit exceeded. Please try again later.", "extensions": { "code": "RATE_LIMITED", "retryAfter": 60 } } ] } ``` **HTTP Status**: 429 Too Many Requests *** ## Best Practices ### 1. Use Pagination Effectively Always check `hasNextPage` to determine if more results exist: ```javascript theme={null} async function fetchAllTransactions() { const allTransactions = []; let offset = 0; const limit = 20; while (true) { const result = await getTransactions({ paginate: { limit, offset } }); allTransactions.push(...result.transactions); if (!result.hasNextPage) break; offset += limit; } return allTransactions; } ``` ### 2. Request Only Needed Fields Specify only the fields you need to reduce response size: ```graphql theme={null} # Good - minimal fields getTransactions { transactions { recordId amount transactionType createdAt } totalCount hasNextPage } # Less efficient - requesting all 40+ fields getTransactions { transactions { recordId transactionType amount ... (all fields) } } ``` ### 3. Use Date Filters for Historical Queries When querying older transactions, always use date filters: ```graphql theme={null} # Good filter: { createdGte: "2024-01-01T00:00:00Z" createdLte: "2024-12-31T23:59:59Z" } ``` ### 4. Cache Settled Transactions Transactions with `status: SETTLED` are immutable and can be cached: ```javascript theme={null} // Example caching strategy const cacheKey = `transactions:${accountId}:${createdGte}:${createdLte}`; let result = cache.get(cacheKey); if (!result) { result = await getTransactions({ filter: { status: ['SETTLED'], createdGte, createdLte } }); cache.set(cacheKey, result, '1 hour'); } ``` ### 5. Combine Filters Efficiently Use range filters to narrow results before applying other filters: ```graphql theme={null} # Efficient - date range first filter: { createdGte: "2025-01-01T00:00:00Z" createdLte: "2025-01-31T23:59:59Z" transactionType: ["GIFT_CARD_PURCHASE"] amountGte: 50.00 } ``` *** ## Rate Limits | Resource | Limit | Window | | ---------------- | ----- | -------- | | Queries per user | 100 | 1 minute | | Queries per IP | 300 | 1 minute | **Headers**: * `X-RateLimit-Limit` - Maximum requests allowed * `X-RateLimit-Remaining` - Requests remaining in current window * `X-RateLimit-Reset` - Time when the rate limit resets (Unix timestamp) *** ## Code Examples ### JavaScript/TypeScript ```typescript theme={null} import { ApolloClient, InMemoryCache, gql } from '@apollo/client'; const client = new ApolloClient({ uri: 'https://transactional-graph.fluzapp.com/api/v1/graphql', cache: new InMemoryCache(), headers: { authorization: `Bearer ${accessToken}`, }, }); const GET_TRANSACTIONS = gql` query GetTransactions($filter: TransactionFilterInput, $paginate: OffsetInput) { getTransactions(filter: $filter, paginate: $paginate) { transactions { recordId transactionType amount description status cashback createdAt } totalCount hasNextPage } } `; async function fetchTransactions() { const { data } = await client.query({ query: GET_TRANSACTIONS, variables: { filter: { status: ['SETTLED'], createdGte: '2025-01-01T00:00:00Z', }, paginate: { limit: 20, offset: 0, }, }, }); return data.getTransactions; } ``` *** ### Python ```python theme={null} import requests def get_transactions(access_token, created_gte=None, created_lte=None, limit=20, offset=0): url = "https://transactional-graph.fluzapp.com/api/v1/graphql" query = """ query GetTransactions($filter: TransactionFilterInput, $paginate: OffsetInput) { getTransactions(filter: $filter, paginate: $paginate) { transactions { recordId transactionType amount description status cashback createdAt } totalCount hasNextPage } } """ variables = { "filter": {}, "paginate": { "limit": limit, "offset": offset } } if created_gte: variables["filter"]["createdGte"] = created_gte if created_lte: variables["filter"]["createdLte"] = created_lte headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } response = requests.post( url, json={"query": query, "variables": variables}, headers=headers ) return response.json()["data"]["getTransactions"] # Usage result = get_transactions( access_token="your_token_here", created_gte="2025-01-01T00:00:00Z", limit=20 ) print(f"Found {result['totalCount']} transactions") print(f"Has more pages: {result['hasNextPage']}") ``` *** ### cURL ```bash theme={null} curl -X POST https://transactional-graph.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "query": "query GetTransactions($filter: TransactionFilterInput, $paginate: OffsetInput) { getTransactions(filter: $filter, paginate: $paginate) { transactions { recordId transactionType amount description status cashback createdAt } totalCount hasNextPage } }", "variables": { "filter": { "status": ["SETTLED"], "createdGte": "2025-01-01T00:00:00Z" }, "paginate": { "limit": 20, "offset": 0 } } }' ``` *** ## FAQ ### Q: What's the maximum number of transactions I can retrieve in one request? **A**: The maximum limit is 20 transactions per request. Use the `hasNextPage` field to implement pagination. ### Q: How far back does transaction history go? **A**: All transactions from account creation are available indefinitely. ### Q: Are pending transactions included? **A**: Yes, pending transactions are included by default. Filter by `status: [SETTLED]` to exclude them. ### Q: What timezone are the timestamps in? **A**: All timestamps are in UTC (ISO 8601 format). ### Q: What scopes do I need? **A**: You need **both** `LIST_PAYMENT` **AND** `LIST_PURCHASES` scopes. ### Q: Can I filter by user ID within my account? **A**: No, the API always returns all transactions for your account. There is no user-level filtering. ### Q: What's the difference between `amount` and `finalAmount` filters? **A**: * `amount` filters the base transaction amount * `finalAmount` filters amount + fees (the total charged to the user) ### Q: How do I filter by date range? **A**: Use `createdGte` and `createdLte` for creation date: ```graphql theme={null} filter: { createdGte: "2025-01-01T00:00:00Z" createdLte: "2025-01-31T23:59:59Z" } ``` *** ## Support * **API Status**: [https://status.fluz.app](https://status.fluz.app) * **Developer Portal**: [https://developers.fluz.app](https://developers.fluz.app) * **Support Email**: [api-support@fluz.app](mailto:api-support@fluz.app) * **Slack Community**: [https://fluz-dev.slack.com](https://fluz-dev.slack.com) *** ## Changelog ### v1.0.0 (Branch: 13-fluz-15659-add-transactions-query-and-webhook-to-api) * Initial release of Transactions Query API * Support for comprehensive filtering (15+ filter types) * Pagination with `TransactionConnection` response type * Balance snapshots included in transaction records * Requires `LIST_PAYMENT` and `LIST_PURCHASES` scopes * Account-level transaction access only *** **Need help?** Contact our developer support team at [api-support@fluz.app](mailto:api-support@fluz.app) or visit our [Developer Portal](https://developers.fluz.app). # Get Card Offers Source: https://docs.fluz.app/features/get-card-offers The `getVirtualCardOffers` query allows you to retrieve a list of active virtual card offers — each offer is a card program you can issue against. **Prerequisites:** a user access token with the `CREATE_VIRTUALCARD` scope. ## Arguments * **`input`** (`GetVirtualCardOffersInput`): Optional input to filter the virtual card offers. ## GetVirtualCardOffersInput fields | Field | Type | Description | Required | | :---------------- | :--------------------- | :---------------------------------------------------------- | :------- | | `cardBrandLocked` | `Boolean` | Specify if virtual card offers are brand locked. | No | | `cardType` | `VirtualCardOfferType` | Specify virtual card network type (`DEBIT` or `PREPAID`). | No | | `cardNetwork` | `VirtualCardNetwork` | Specify virtual card network type (`MASTERCARD` or `VISA`). | No | ## Sample query ```graphql theme={null} query GetVirtualCardOffers { getVirtualCardOffers { offerId bin bankName rewardValue programLimits { dailyLimit weeklyLimit monthlyLimit } programName } } ``` ## cURL example ```curl theme={null} curl -X POST \ https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \ -d '{ "query": "query { getVirtualCardOffers(input: { cardType: DEBIT, cardNetwork: MASTERCARD }) { offerId programName bin bankName programLimits { dailyLimit weeklyLimit monthlyLimit } rewardValue } }" }' ``` ## Sample response Offers are sorted by rewardValue. ```json theme={null} { "data": { "getVirtualCardOffers": [ { "offerId": "offer-id-1", "programName": "Example Card Program A", "bin": "543210", "bankName": "Bank of Examples", "programLimits": { "dailyLimit": "500.00", "weeklyLimit": "2000.00", "monthlyLimit": "5000.00" }, "rewardValue": "1.5%" }, { "offerId": "offer-id-2", "programName": "Example Card Program B", "bin": "456789", "bankName": "Another Bank", "programLimits": { "dailyLimit": "1000.00", "weeklyLimit": "4000.00", "monthlyLimit": "10000.00" }, "rewardValue": "1%" } ] } } ``` ## Response fields | Field | Type | Description | | ---------------------------- | --------------- | ---------------------------------------------------------------------------- | | `offerId` | `String` | The unique identifier for the virtual card offer. | | `programName` | `String` | The name of the virtual card program or merchant. | | `bin` | `String` | The Bank Identification Number (BIN) associated with the virtual card offer. | | `bankName` | `String` | The name of the issuing bank for this virtual card offer. | | `programLimits` | `ProgramLimits` | An object detailing the spending limits for this program. | | `programLimits.dailyLimit` | `String` | The maximum amount that can be spent daily on cards from this program. | | `programLimits.weeklyLimit` | `String` | The maximum amount that can be spent weekly on cards from this program. | | `programLimits.monthlyLimit` | `String` | The maximum amount that can be spent monthly on cards from this program. | | `rewardValue` | `String` | The earn rate or reward percentage offered by this virtual card program. | ## Next steps Pass the `offerId` you picked into `createVirtualCard`. Merchant-specific offers that layer additional rewards on top of the base program. # Get Declined Transactions Source: https://docs.fluz.app/features/get-decline-transactions ## Overview The Declined Transactions API allows you to retrieve a comprehensive history of all financial transactions associated with your account that were declined/failed. This includes purchases, deposits, withdrawals, transfers, bill payments, and all other financial activities. **Endpoint Type**: GraphQL Query\ **Authentication**: Required (JWT Bearer Token)\ **Rate Limit**: Standard GraphQL rate limits apply **Authorization required** This mutation requires `LIST_PAYMENT` & `LIST_PURCHASES` scopes. Ensure your access token has been granted this scope before attempting to get a list of declined transactions. *** ## Declined Transaction The DeclineTransaction object contains information. Here's the table: | Field name | Type | Description | | :-------------------------- | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transactionId` | `UUID!` | Unique identifier of the declined transaction | | `transactionType` | `String!` | Type of transaction that was declined (e.g. "Gift Card Purchase", "Virtual Card Purchase", "Add Money", "Withdrawal", "Transfer - Out", "Bill Payment Initiation", "Issue Virtual Card") | | `amount` | `Float!` | Total transaction amount attempted, in the transaction's currency | | `fluzAmount` | `Float!` | Portion funded from Fluz internal balances (cash/rewards/reserve/prepayment). Defaults to 0 when not applicable | | `externalFundingAmount` | `Float!` | Portion funded from an external source (e.g. linked bank/card). Defaults to 0 when not applicable | | `currency` | `String!` | ISO 4217 currency code. Defaults to "USD" when not specified | | `sourceRecordType` | `String` | Type of the originating source record (e.g. originating domain/entity type) | | `sourceRecordId` | `String` | Identifier of the originating source record | | `accountId` | `String!` | Identifier of the Fluz account that owns the transaction | | `userId` | `String` | Identifier of the user who attempted the transaction. May be absent for account-level/system transactions | | `source` | `String` | Source of funds / origin label (e.g. funding source or originating party) | | `destination` | `String` | Destination label (e.g. merchant or recipient). Typically the merchant for merchant transactions | | `status` | `DeclinedTransactionStatus!` | Outcome status: `DECLINED` or `FAILED` | | `merchantId` | `String` | Identifier of the associated merchant, when known | | `descriptorId` | `String` | Identifier of the resolved merchant descriptor used to enrich merchant metadata | | `liabilityId` | `String` | Identifier of the associated liability record, when applicable | | `merchantName` | `String` | Display name of the merchant. Populated from destination when a merchant is identified | | `merchantCountry` | `String` | Country of the merchant | | `merchantCity` | `String` | City of the merchant | | `merchantState` | `String` | State/region of the merchant | | `logoUrl` | `String` | URL of the logo to display (typically the merchant/brand logo) | | `category` | `String` | Category/classification of the transaction or merchant | | `cardLastFour` | `String` | Last four digits of the card used | | `cardDisplayName` | `String` | Human-friendly display name of the card used | | `virtualCardId` | `String` | Identifier of the virtual card involved, when applicable | | `channel` | `String` | Channel through which the transaction was initiated (e.g. app, web, integration) | | `externalFundingSourceType` | `String` | Type of external funding source used (e.g. bank account, debit card) | | `externalFundingSourceId` | `String` | Identifier of the external funding source used | | `fundingSourceSubtype` | `String` | Subtype of the funding source (e.g. checking vs. savings, card network subtype) | | `isCashBalanceUsed` | `Boolean!` | True if the user's cash balance was applied/attempted | | `isPrepaymentBalanceUsed` | `Boolean!` | True if the gift-card prepayment balance was applied/attempted | | `isRewardsBalanceUsed` | `Boolean!` | True if the rewards balance was applied/attempted | | `isReserveBalanceUsed` | `Boolean!` | True if the reserve balance was applied/attempted | | `transactionDateTime` | `String!` | Timestamp of the transaction as an ISO 8601 string (derived from creation time) | | `spendAccountNickname` | `String` | Nickname of the spend account used. Falls back to "Main account" when not set | | `declineTitle` | `String` | Short user-facing title summarizing the decline | | `declineReason` | `String` | User-facing reason explaining why the transaction was declined | | `declineDescription` | `String` | Longer user-facing description with additional decline detail | | `declineCtaText` | `String` | Call-to-action text suggesting the next step for the user | | `declineCategory` | `String` | Categorization of the decline (used to group/route decline messaging) | | `offerId` | `UUID` | Identifier of the associated offer, when applicable | | `issuer` | `String` | Card issuer associated with the card/transaction | | `bin` | `String` | Bank Identification Number (first digits) of the card used | | `limitAmount` | `String` | Spend-limit amount relevant to the decline (e.g. the limit exceeded) | | `limitDuration` | `String` | Duration/window the spend limit applies to (e.g. per-transaction, daily, monthly) | | `lockOnNextUse` | `String` | Indicates whether the card is set to lock on next use | | `lockDate` | `String` | Date associated with a card lock, when applicable | | `bankAccountNickname` | `String` | Nickname of the bank account associated with the funding source | | `bankAccountLastFour` | `String` | Last four digits of the bank account associated with the funding source | | `isPrivate` | `Boolean` | Privacy flag indicating whether the transaction should be treated as private | | `createdAt` | `DateTime!` | Timestamp when the transaction record was created | | `updatedAt` | `DateTime!` | Timestamp when the transaction record was last updated | ## Quick Start To obtain a list of declined transactions associated with your account, use the `getDeclinedTransactions` query. This call returns a list of declined transactions details, including the `transactionId`, `amount` , `status` & `destination`. ### Variables | Field | Type | Required | Description | | ---------- | ---------------------------- | -------- | --------------------------------------------------- | | `filter` | `UserCashBalanceFilterInput` | No | Filtering criteria for spend accounts | | `paginate` | `OffsetInput` | No | Pagination parameters (default: limit=20, offset=0) | ### Sample Request ```graphql theme={null} query GetDeclinedTransactions( $filter: DeclinedTransactionFilterInput $paginate: OffsetInput ) { getDeclinedTransactions(filter: $filter, paginate: $paginate) { totalCount hasNextPage transactions { transactionId transactionType amount fluzAmount externalFundingAmount currency status accountId userId source destination merchantId merchantName merchantCity merchantState merchantCountry logoUrl category cardLastFour cardDisplayName virtualCardId declineTitle declineReason declineDescription declineCtaText declineCategory isCashBalanceUsed isPrepaymentBalanceUsed isRewardsBalanceUsed isReserveBalanceUsed externalFundingSourceType externalFundingSourceId fundingSourceSubtype bankAccountNickname bankAccountLastFour channel offerId issuer bin limitAmount limitDuration lockOnNextUse lockDate isPrivate createdAt updatedAt } } } ``` Please note that due to rate-limiting, you might need to paginate and call the declined transactions list a few times. ### Sample Response The response will contain a list of declined transactions ```json theme={null} { "data": { "getDeclinedTransactions": { "totalCount": 1, "hasNextPage": false, "transactions": [ { "transactionId": "237b8450-7d2c-4233-9227-420bd7ed569d", "transactionType": "Virtual Card Purchase", "amount": 125.00, "fluzAmount": 0.00, "externalFundingAmount": 0.00, "currency": "USD", "status": "DECLINED", "accountId": "58c46f99-472f-4c5b-a4b5-776fa757263a", "userId": "23dff16a-773f-4c5d-accf-a61e70f1afa7", "source": "ACH Plaid Silver Standard 0.1% Interest Saving 1111", "destination": "Uber", "sourceRecordType": "VIRTUAL_CARD_TRANSACTION_ACTIVITY", "sourceRecordId": "3b6e6b77-d720-4679-8461-919e0b28df00", "merchantId": "9f83fa82-435e-4553-9525-8c7871825f74", "descriptorId": "2f30bea0-33a9-4b4b-95ce-b19662821d45", "liabilityId": null, "merchantName": "Uber", "merchantCountry": "USA", "merchantCity": null, "merchantState": null, "logoUrl": "https://storage.googleapis.com/fluz-fluz-file-uploads-prod-ricuyxowbwlfprel/UBER-logo.jpg", "category": "Miscellaneous Specialty Retail", "cardLastFour": "3258", "cardDisplayName": null, "virtualCardId": "73b9708a-92e4-45d5-930c-ae24e4997f91", "channel": null, "externalFundingSourceType": "BANK_ACCOUNT", "externalFundingSourceId": "7bc2c9ad-fd58-4be1-b782-284c59f54900", "fundingSourceSubtype": "SAVING", "isCashBalanceUsed": true, "isPrepaymentBalanceUsed": true, "isRewardsBalanceUsed": true, "isReserveBalanceUsed": false, "transactionDateTime": "2026-06-23T15:44:33.928Z", "spendAccountNickname": "Main account", "declineTitle": "Insufficient funds", "declineReason": "There aren't enough funds available on this card. Would you like to add funds?", "declineDescription": "Your $125.00 transaction was declined because your funding source has insufficient funds.", "declineCtaText": "Add money", "declineCategory": "Virtual Card", "offerId": null, "issuer": null, "bin": null, "limitAmount": null, "limitDuration": null, "lockOnNextUse": null, "lockDate": null, "bankAccountNickname": "ACH Plaid Silver Standard 0.1% Interest Saving", "bankAccountLastFour": "1111", "isPrivate": false, "createdAt": "2026-06-23T15:44:33.928Z", "updatedAt": "2026-06-23T15:44:41.205Z" } ] } } } ``` ### Filter Options | Field | Type | Description | | :------------------ | :---------------------------- | :----------------------------------------------------------------------------------- | | `transactionId` | `[UUID]` | Filter by specific transaction record IDs | | `status` | `[DeclinedTransactionStatus]` | Filter by status (`DECLINED` or `FAILED`). Defaults to both when omitted | | `amount` | `Float` | Filter by exact amount | | `amountGte` | `Float` | Filter by minimum amount (greater than or equal) | | `amountLte` | `Float` | Filter by maximum amount (less than or equal) | | `fluzAmount` | `Float` | Filter by exact Fluz amount | | `fluzAmountGte` | `Float` | Filter by minimum Fluz amount (greater than or equal) | | `fluzAmountLte` | `Float` | Filter by maximum Fluz amount (less than or equal) | | `createdGte` | `DateTime` | Filter by creation date (greater than or equal) | | `createdLte` | `DateTime` | Filter by creation date (less than or equal) | | `updatedGte` | `DateTime` | Filter by last updated date (greater than or equal) | | `updatedLte` | `DateTime` | Filter by last updated date (less than or equal) | | `merchantId` | `[UUID]` | Filter by merchant IDs | | `merchant` | `[String]` | Filter by merchant names (matched against `destination` field) | | `transactionType` | `[String]` | Filter by transaction types (e.g. `"Gift Card Purchase"`, `"Virtual Card Purchase"`) | | `channel` | `[String!]` | Filter by channel (e.g. `"UWP"`, `"app"`) | | `category` | `[String]` | Filter by transaction or merchant category | | `virtualCardId` | `[UUID]` | Filter by specific virtual card IDs | | `fundingSource` | `[String]` | Filter by funding source names | | `userCashBalanceId` | `[UUID]` | Filter by spend account IDs | | `liabilityId` | `UUID` | Filter by liability ID (for bill payment transactions) | **Example - Get only** `DECLINED `**spend accounts** ```json theme={null} { "filter": { "status": [DECLINED] } } ``` ### Pagination | Field | Type | Default | Max | Description | | -------- | ----- | ------- | --- | ----------------------------------------- | | `limit` | `Int` | 20 | 20 | Number of transactions to return per page | | `offset` | `Int` | 0 | - | Number of transactions to skip | **Example - Page 1**: ```json theme={null} { "paginate": { "limit": 20, "offset": 0 } } ``` **Example - Page 2**: ```json theme={null} { "paginate": { "limit": 20, "offset": 20, } } ```
*** ## Best Practices ### 1. Use Pagination Effectively Always check `hasNextPage` to determine if more results exist: ```javascript theme={null} async function fetchDeclinedTransactions() { const transactions = []; let offset = 0; const limit = 20; while (true) { const result = await getDeclinedTransactions({ paginate: { limit, offset } }); transactions.push(...result.transactions); if (!result.hasNextPage) break; offset += limit; } return transactions; } ``` ### 2. Request Only Needed Fields Specify only the fields you need to reduce response size: ```graphql theme={null} # ✅ Good - minimal fields getDeclinedTransactions { transactions { transactionId transactionType amount createdAt } totalCount hasNextPage } # ❌ Less efficient - requesting all 40+ fields getDeclinedTransactions { transactions { transactionId transactionType amount ... (all fields) } } ``` ### 3. Use Date Filters for Historical Queries When querying older transactions, always use date filters: ```graphql theme={null} # ✅ Good filter: { createdGte: "2024-01-01T00:00:00Z" createdLte: "2024-12-31T23:59:59Z" } ``` ### 5. Combine Filters Efficiently Use range filters to narrow results before applying other filters: ```graphql theme={null} # ✅ Efficient - date range first filter: { createdGte: "2025-01-01T00:00:00Z" createdLte: "2025-01-31T23:59:59Z" transactionType: ["GIFT_CARD_PURCHASE"] amountGte: 50.00 } ``` *** ## Rate Limits | Resource | Limit | Window | | ---------------- | ----- | -------- | | Queries per user | 100 | 1 minute | | Queries per IP | 300 | 1 minute | **Headers**: * `X-RateLimit-Limit` - Maximum requests allowed * `X-RateLimit-Remaining` - Requests remaining in current window * `X-RateLimit-Reset` - Time when the rate limit resets (Unix timestamp)
# Get Gift Card Purchases Source: https://docs.fluz.app/features/get-gift-card-purchases To view historical gift card purchases and virtual cards on your account, use the `getUserPurchases` [query](/api-reference/overview). The query requires `LIST_PURCHASES` scope. This query response can get bulky, so we ask you to provide a pagination input, and an optional filter input: ## Pull Purchase History You can use the following query in order to view your transactions. 1. `getUserPurchases` — Get User Purchases ## Pagination Input The `OffsetInput` argument is used to manage pagination in API responses, allowing you to control how many items are returned per page and how many items should be skipped from the beginning of the list. ### Fields **`limit`** **(Int)**: This field specifies the number of items to return per page. The maximum number of items you can request per page is 20. If you do not specify a value for limit, it will default to 20. **`offset`** **(Int)**: This field specifies the number of items to skip before starting to return the items. If you do not specify a value for offset, it will default to 0, meaning it will start from the first item in the list. ## Filter Input The `UserPurchaseFilterInput` argument is used to filter the purchases returned in the response. It allows you to retrieve purchases made by the user associated to your app, or purchases made under the account associated to your app, etc. It is optional. If it is not provided, the response will return purchases made by the user associated to your app, across all accounts. ### Fields **`scope`** **(Array of `PurchaseScopeFilter`)**: This field specifies the type of filter to use. ##### `PurchaseScopeFilter` enum * TOKEN\_USER: filter based on userId * TOKEN\_ACCOUNT: filter based on accountId ### Examples ```javascript theme={null} // QUERY VARIABLES // returns purchases made by the user across all accounts { "filter": { "scope": ["TOKEN_USER"] } } ``` ```javascript theme={null} // QUERY VARIABLES // returns purchases made under the account across all users { "filter": { "scope": ["TOKEN_ACCOUNT"] } } ``` ```javascript theme={null} // QUERY VARIABLES // returns purchases made by the user and account { "filter": { "scope": ["TOKEN_USER", "TOKEN_ACCOUNT"] } } ``` # Response When you run this query, it will respond with an array of your historical gift card purchases and generated virtual cards. ```json theme={null} { "data": { "getUserPurchases": [ { "purchaseId": "255f8245-02c7-4817-901e-15fe265f6968", "purchaseDisplayId": "1019688", "purchaseBankCardId": "255f8245-02c7-4817-901e-15fe265f6968", "bankAccountId": "255f8245-02c7-4817-901e-15fe265f6968", "purchaseAmount": 987.65, "fluzpayAmount": 123.45, "seatRewardValue": 987.65, "paypalVaultId": "255f8245-02c7-4817-901e-15fe265f6968", "createdAt": "2007-12-03T10:15:30Z", "purchaserUserId": "4c42e368-d150-492d-8dec-6cda1b58ffa9", "accountId": "d67af480-21dc-4c23-93ff-3b7288e52e1a", "giftCard": { // GiftCardFragment details }, "virtualCard": { // VirtualCardFragment details } }, { "purchaseId": "4f4b3c5d-6f7e-8a9b-0c1d-23e4f5678a90", "purchaseDisplayId": "1019688", "purchaseBankCardId": "4f4b3c5d-6f7e-8a9b-0c1d-23e4f5678a90", "bankAccountId": "4f4b3c5d-6f7e-8a9b-0c1d-23e4f5678a90", "purchaseAmount": 250.00, "fluzpayAmount": 50.00, "seatRewardValue": 10.00, "paypalVaultId": "4f4b3c5d-6f7e-8a9b-0c1d-23e4f5678a90", "createdAt": "2024-01-10T08:20:00Z", "giftCard": { // GiftCardFragment details }, "virtualCard": { // VirtualCardFragment details } }, { "purchaseId": "7e8f9a0b-1c2d-3e4f-5g6h-789i0jklm1no", "purchaseDisplayId": "1019688", "purchaseBankCardId": "7e8f9a0b-1c2d-3e4f-5g6h-789i0jklm1no", "bankAccountId": "7e8f9a0b-1c2d-3e4f-5g6h-789i0jklm1no", "purchaseAmount": 450.75, "fluzpayAmount": 75.00, "seatRewardValue": 30.00, "paypalVaultId": "7e8f9a0b-1c2d-3e4f-5g6h-789i0jklm1no", "createdAt": "2024-02-15T14:45:00Z", "giftCard": { // GiftCardFragment details }, "virtualCard": { // VirtualCardFragment details } } ] } } ``` ## Fields in the UserPurchase Object | Field name | Type | Description | | :----------------- | :---------- | :---------------------------------------------------------------------------------------------------- | | purchaseId | String | A unique identifier for the purchase. | | purchaseDisplayId | String | The display ID of the purchase. | | purchaseBankCardId | String | The identifier of the bank card used for the purchase. | | bankAccountId | String | The identifier of the bank account associated with the purchase. | | purchaseAmount | Float | The total amount spent in the purchase. | | fluzpayAmount | Float | The amount of available Fluz balance used in the transaction. | | seatRewardValue | Float | The value of any network seat rewards generated from the purchase. | | paypalVaultId | String | The identifier of the PayPal vault used for the purchase. | | createdAt | DateTime | The timestamp indicating when the purchase was made. | | purchaserUserId | String | The identifier of the user who made the purchase. | | accountId | String | The identifier of the account who made the purchase. | | giftCard | GiftCard | Contains details about any gift card used in the purchase, represented by the GiftCardFragment. | | virtualCard | VirtualCard | Contains details about any virtual card used in the purchase, represented by the VirtualCardFragment. | # Get Spend Accounts Source: https://docs.fluz.app/features/get-spend-accounts After creating a spend account, you can access it through the following queries: 1. `getUserCashBalances` - retrieves a list of all your spend accounts. 2. `getUserCashBalanceById` - retrieves a specific spend account based on it's id value. **Authorization required** This mutation requires the `LIST_PAYMENT` scope. Ensure your access token has been granted this scope before attempting to transfer funds internally. ## Spend Account details The `UserCashBalanceDetails` object will contain important information about the spend account. | Field name | Type | Description | | :------------------- | :--------------------- | :--------------------------------------------------------- | | userCashBalanceId | UUID! | Unique identifier for the cash balance account | | totalCashBalance | String! | Total cash balance in the account (starts at 0) | | availableCashBalance | String! | Available cash balance for immediate use (starts at 0) | | lifetimeCashBalance | String! | Cumulative total of all funds ever deposited (starts at 0) | | nickname | String | The custom name assigned to the account | | status | UserCashBalanceStatus! | Current status of the account (ACTIVE, CLOSED) | | isDefault | Boolean! | Whether spend account is the default one | | createdAt | DateTime! | Timestamp when the account was created | | updatedAt | DateTime! | Timestamp when the account was updated | ## Retrieve the Spend Account List To obtain a list of spend accounts created on your account, use the `getUserCashBalances` query. This call returns a list of spend accounts with basic details, including the `userCashBalanceId`. You will need this ID to retrieve a specific spend account through `getuserCashBalanceById`. ### Variables | Field | Type | Required | Description | | -------- | -------------------------- | -------- | --------------------------------------------------- | | filter | UserCashBalanceFilterInput | No | Filtering criteria for spend accounts | | paginate | OffsetInput | No | Pagination parameters (default: limit=20, offset=0) | ### Sample Request ```graphql theme={null} query GetUserCashBalances( $filter: UserCashBalanceFilterInput, $paginate: OffsetInput, ) { getUserCashBalances( filter: $filter paginate: $paginate ) { userCashBalances { userCashBalanceId totalCashBalance availableCashBalance lifetimeCashBalance nickname status isDefault createdAt updatedAt } totalCount hasNextPage } } ``` Please note that due to rate-limiting, you might need to paginate and call the spend account list a few times. ### Sample Response The response will contain a list of spend accounts ```json JSON theme={null} { "data": { "getUserCashBalances": { "userCashBalances": [ { "userCashBalanceId": "c115604e-5d47-473c-a955-41a820cdcaf8", "totalCashBalance": "0.00", "availableCashBalance": "0.00", "lifetimeCashBalance": "768.00", "nickname": "Virtual Prepaid Account", "status": "ACTIVE", "isDefault": false, "createdAt": "2025-10-10T15:30:02.535Z", "updatedAt": "2026-04-22T16:07:18.618Z" }, { "userCashBalanceId": "1c1b5fcc-eb21-44c5-b678-9c41f3fa21b4", "totalCashBalance": "36.00", "availableCashBalance": "36.00", "lifetimeCashBalance": "177.00", "nickname": "austin1", "status": "ACTIVE", "isDefault": false, "createdAt": "2026-02-27T22:18:28.814Z", "updatedAt": "2026-04-22T15:43:52.031Z" }, { "userCashBalanceId": "6d1b4b19-deef-42f5-80d7-ec34804ce090", "totalCashBalance": "425108.80", "availableCashBalance": "425108.80", "lifetimeCashBalance": "444096.44", "nickname": "Main account", "status": "ACTIVE", "isDefault": true, "createdAt": "2025-08-01T19:25:54.392Z", "updatedAt": "2026-04-21T19:19:22.777Z" } ], "totalCount": 3, "hasNextPage": false } } } ``` ### Filter Options | Field | Type | Description | | :---------------- | :------------------------ | :------------------------------------------------- | | userCashBalanceId | \[UUID!] | Filter by specific spend account IDs | | nickname | \[String!] | Filter by spend accounts nicknames | | status | \[UserCashBalanceStatus!] | Filter by spend account status | | isDefault | Boolean | Filter by whether spend account is the default one | | createdGte | DateTime | Filter by creation date (greater than or equal) | | createdLte | DateTime | Filter by creation date (less than or equal) | | updatedGte | DateTime | Filter by update date (greater than or equal) | | updatedLte | DateTime | Filter by update date (less than or equal) | **Example - Get only `ACTIVE` spend accounts** ```json theme={null} { "filter": { "status": [ACTIVE] } } ``` ### Pagination | Field | Type | Default | Max | Description | | ------ | ---- | ------- | --- | ----------------------------------------- | | limit | Int | 20 | 20 | Number of transactions to return per page | | offset | Int | 0 | - | Number of transactions to skip | **Example - Page 1**: ```json theme={null} { "paginate": { "limit": 20, "offset": 0 } } ``` **Example - Page 2**: ```json theme={null} { "paginate": { "limit": 20, "offset": 20, } } ``` ## Retrieve a specific Spend Account To obtain a specific spend account created on your account, use the `getUserCashBalanceById` query. This call returns this spend account's details, including the `userCashBalanceId` & it's balances. ### Variables | Field | Type | Required | Description | | ----------------- | ----- | -------- | ------------------ | | userCashBalanceId | UUID! | Yes | Spend account's ID | ### Sample Request ```graphql theme={null} query GetUserCashBalanceById( $userCashBalanceId: UUID! ) { getUserCashBalanceById( userCashBalanceId: $userCashBalanceId ) { userCashBalanceId totalCashBalance availableCashBalance lifetimeCashBalance nickname status isDefault createdAt updatedAt } } ``` ### Sample Response The response will contain a specific spend account's details ```json JSON theme={null} { "data": { "getUserCashBalanceById": { "userCashBalanceId": "6d1b4b19-deef-42f5-80d7-ec34804ce090", "totalCashBalance": "425108.80", "availableCashBalance": "425108.80", "lifetimeCashBalance": "444096.44", "nickname": "Main account", "status": "ACTIVE", "isDefault": true, "createdAt": "2025-08-01T19:25:54.392Z", "updatedAt": "2026-04-21T19:19:22.777Z" } } } ```
# Get Virtual Card Transactions Source: https://docs.fluz.app/features/get-virtual-card-transactions Retrieve transactions for one or more virtual cards, with support for filtering and pagination. Use `getVirtualCardTransactions` to query virtual card activity by card ID, transaction type, date range, or pagination options. * Provide `virtualCardIds` to fetch transactions for specific cards. * Omit `virtualCardIds` to fetch transactions across all cards on the authenticated account. **Required scopes:** `PCI_COMPLIANCE`, `REVEAL_VIRTUALCARD` ## Arguments ### `input` — `GetVirtualCardTransactionsInput` | Field | Type | Required | Description | | ---------------- | -----------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `virtualCardIds` | `[UUID!]` | No | List of virtual card IDs to retrieve transactions for. Limited to 10 IDs when provided. If omitted, transactions are returned across all virtual cards on the authenticated account. | | `filters` | `VirtualCardTransactionFiltersInput` | No | Filters used to narrow the transaction results. | | `paginate` | `OffsetInput` | No | Pagination controls for limiting and offsetting results. | ## Filters | Field | Type | Required | Description | | ------------------ | ----------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------ | | `transactionTypes` | `[VirtualCardTransactionListType!]` | No | Filter by transaction type. Supported values include `PURCHASE`, `REFUND`, and `DECLINE`. | | `dateRangeStart` | `String` | No | Inclusive start of the transaction date range in ISO 8601 format. Must be used with `dateRangeEnd`. | | `dateRangeEnd` | `String` | No | Inclusive end of the transaction date range in ISO 8601 format. Must be greater than or equal to `dateRangeStart`. | ## Pagination | Field | Type | Description | | -------- | ----- | ---------------------------------------------------------- | | `limit` | `Int` | Maximum number of transactions to return. Capped at `500`. | | `offset` | `Int` | Number of transactions to skip. | **Defaults:** * When `virtualCardIds` is omitted and `limit` is not specified, the default `limit` is **100**. * When `virtualCardIds` is provided and `limit` is not specified, all matching transactions for each card may be returned. We recommend specifying a `limit` to control response size. **Behavior:** * When `virtualCardIds` is provided, `limit` and `offset` apply **per card**. * When `virtualCardIds` is omitted, `limit` and `offset` apply **across the authenticated account**. > **Note:** Requesting multiple cards with a high `limit` may result in large responses. ## Validation Errors The following inputs are rejected before execution: * `dateRangeStart` and `dateRangeEnd` must be provided together. * `dateRangeEnd` must be greater than or equal to `dateRangeStart`. * Both must be valid ISO 8601 timestamps. * `paginate.limit` cannot exceed 500. * `virtualCardIds`, when provided, must contain between 1 and 10 valid UUIDs. ## Example: Specific cards ```graphql theme={null} query GetVirtualCardTransactions { getVirtualCardTransactions( input: { virtualCardIds: [ "2ed71ba0-d457-47ed-8ceb-d3fe6ce5c900" "bd3be748-a0fb-4193-80a7-88419bc72dab" ] filters: { transactionTypes: [PURCHASE, REFUND] } paginate: { limit: 20 offset: 0 } } ) { virtualCardId transactions { transactionDate transactionType transactionStatus transactionAmount merchantName mcc merchantCountryCode originalCurrencyCode originalCurrencyAmount currencyConversionRate } } } ``` ## Example: All cards (date range) Use this when you do not know which cards had activity during a specific time period. ```graphql theme={null} query GetAccountTransactionsByDate { getVirtualCardTransactions( input: { filters: { dateRangeStart: "2026-04-01T00:00:00Z" dateRangeEnd: "2026-04-30T23:59:59Z" } paginate: { limit: 100 offset: 0 } } ) { virtualCardId transactions { transactionDate transactionType transactionAmount merchantName mcc merchantCountryCode originalCurrencyCode originalCurrencyAmount currencyConversionRate } } } ``` ## cURL Example ```bash theme={null} curl -X POST \ https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \ -d '{ "query": "query { getVirtualCardTransactions(input: { filters: { dateRangeStart: \"2026-04-01T00:00:00Z\", dateRangeEnd: \"2026-04-30T23:59:59Z\" }, paginate: { limit: 100, offset: 0 } }) { virtualCardId transactions { transactionDate transactionType transactionAmount merchantName mcc merchantCountryCode originalCurrencyCode originalCurrencyAmount currencyConversionRate } } }" }' ``` ## Response Fields * `virtualCardId` (`UUID`) — The virtual card associated with the returned transactions. * `transactions` (`[VirtualCardTransaction!]`) — List of transactions for the virtual card. * `transactionDate` (`String`) — Date and time the transaction occurred in ISO 8601 format. * `transactionType` (`String`) — Transaction type, such as `PURCHASE`, `REFUND`, or `DECLINE`. * `transactionAmount` (`Float`) — Transaction amount in USD. May be `null`. * `transactionStatus` (`String`) — Lifecycle status, such as `CLEARED` or `PROCESSING`. * `transactionApproval` (`String`) — Approval status of the transaction. * `transactionResponseCode` (`String`) — Card network response code. * `merchantName` (`String`) — Merchant name when available. May be `null`. * `paymentMethod` (`String`) — Payment method used for the transaction. * `mcc` (`Int`) — Merchant category code. May be `null`. * `merchantCountryCode` (`String`) — Merchant country code. May be `null`. * `originalCurrencyCode` (`String`) — ISO 4217 currency code of the original transaction (e.g. USD, HKD, EUR). May be null when the original currency is unknown or not applicable. * `originalCurrencyAmount` (`Float`) — Original amount in the currency’s minor units. For example, `6300` for HKD means `HK$63.00`. * `currencyConversionRate` (`Float`) — FX conversion rate used to convert the original currency to USD. `1.0` for USD transactions. ## Notes * FX fields are returned together. They are either all populated or all null. * `originalCurrencyAmount` is returned in minor units: * HKD 6300 = HK\$63.00 * JPY 100 = ¥100 * KWD 1000 = 1.000 KD ### Code Example:
# Getting Bank Account Transaction Data Source: https://docs.fluz.app/features/getting-bank-data Read historical bank transactions for a linked Plaid account. These transactions are ingested and maintained by identity-service; TGS exposes them read-only. ## Auth Call `/api/v1/graphql` with a Fluz user Bearer access token that includes `MANAGE_PAYMENT`. Basic auth is not allowed on these fields. ```http theme={null} Authorization: Bearer ``` ## Get transactions ```graphql theme={null} query GetPlaidBankTransactions($input: PlaidBankTransactionFilterInput) { getPlaidBankTransactions(input: $input) { totalCount transactions { historicalBankTransactionId platformItemId bankInstitutionAuthId bankAccountId transactionId transactionStatus transactionType transactionDate amount currencyCode description merchantName category pending } } } ``` ```json theme={null} { "input": { "bankAccountId": "bank-account-id", "startDate": "2026-01-01T00:00:00.000Z", "endDate": "2026-06-04T23:59:59.999Z", "paginate": { "limit": 50, "offset": 0 } } } ``` **Notes** * Filter by `bankAccountId` and a `startDate`/`endDate` window; page through results with `paginate.limit` and `paginate.offset`. * `totalCount` reflects the total matching the filter, for building pagination. * `pending: true` marks transactions that have not fully settled yet — the same recent, unsettled outflows that reduce a user's available spend power (see *Managing Bank Account Spend Power*).
# Manage linked bank accounts Source: https://docs.fluz.app/features/link-external-bank-accounts A **bank account** is the lowest-cost, highest-reward funding source on Fluz. Bank accounts are linked through **Plaid** and settle via **ACH** — they carry no processing fees and earn the full merchant cashback rate. Bank account linking and management is exposed through a public GraphQL wrapper. Identity-service remains the system of record for Plaid tokens, account ingestion, balances, historical transactions, and Plaid webhooks. **Bank accounts carry no processing fees and earn the highest cashback rate.** On a 4% merchant offer, an ACH payment returns the full 4%, while a debit card returns 3% and a credit card 1% after processing fees. Where a user has the choice, a linked bank account is always the best-earning option. *** ## What You Can Do Everything below runs through the Plaid integration. See [Linking Bank Account via Plaid](/features/link-via-plaid) for full request and response details. ### Linking | Capability | Operation | Description | | -------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | | Create a Link token | `createPlaidLinkToken` | Generate the token used to open Plaid Link. | | Complete a new link | `completePlaidLink` | Exchange Plaid's `public_token` and ingest the connected accounts. | | Repair a broken connection | `createPlaidLinkToken` + `completePlaidLink` (with `platformItemId`) | Relink a previously connected institution that has disconnected. | | Attach a required address | `createPlaidLinkAddress` | Add an address when `completePlaidLink` returns `requiresAddress: true`. | ### Balances & Spend Power | Capability | Operation | Description | | ----------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------- | | List linked accounts | `getPlaidBankAccounts` | Return the safe metadata for all connected bank accounts. | | Read stored balances | `getPlaidBankBalances` · `getPlaidBankAccountBalance` | Get the latest stored balance across accounts or for one account. | | Read spend power | `getPlaidBankAccountSpendPower` | Return available spend power, last recorded balance, and pending activity. | | Refresh one balance | `refreshPlaidBankAccountBalance` | Request a rate-limited realtime balance refresh for one account. | | Refresh all connections | `refreshPlaidBankConnections` | Request a cached refresh across all connected Plaid data. | ### Transactions | Capability | Operation | Description | | ------------------------ | -------------------------- | --------------------------------------------------------------------------------- | | Read transaction history | `getPlaidBankTransactions` | Return historical, paginated bank transactions with merchant and category detail. | ### Management | Capability | Operation | Description | | --------------------- | ---------------------------- | ------------------------------------------------------ | | Remove an institution | `removePlaidBankInstitution` | Disconnect a linked bank institution and its accounts. | *** ## Requirements All bank account operations require a Fluz user access token that includes the `MANAGE_PAYMENT` scope. **Bearer auth only.** The Plaid fields do not accept Basic auth. Obtain a user access token through the standard authentication flow first, then call the Plaid fields with `Authorization: Bearer `. *** ## Balance Refresh Limits Balance refreshes are cost-controlled per Plaid institution: * **One** realtime refresh per hour * **Six** realtime refreshes per day Calls made inside the limit window return the latest stored balance instead of triggering a new realtime request. *** ## Savings Accounts **Linking a login connects every account under it, including savings.** Most banks do not permit payments from savings accounts. After linking, users should remove any savings accounts they don't intend to pay from to avoid failed transactions. *** ## Unsupported Flows Micro-deposit linking is intentionally unsupported. New links and relinks must use the standard Plaid Link flow, which supports balances and historical transactions. *** ## Security We do not store Plaid access tokens, Plaid public tokens, identity-match details, bank account numbers, or routing numbers. Only safe account metadata — such as institution name, last four digits, type, and user-entered labels — is exposed through the API. *** **Want to learn more?** Speak with our experts for more info or to request a demo. # Manage linked bank cards Source: https://docs.fluz.app/features/link-external-cards A **bank card** — debit, credit, or prepaid — is the most flexible funding source on Fluz and the only one that can be added programmatically. Bank cards are used to pay for gift card purchases, fund virtual card transactions, deposit to your Fluz balance, and serve as the required backup for other payment methods. Bank cards can be added and managed through the **API** or through the Fluz app and web portal. **A bank card must be added as a backup payment method before any transaction can complete.** When a user pays via ACH, a temporary hold may be placed on the backup card for the transaction amount. If the ACH payment clears, the hold releases within 1–7 business days. If it does not clear, the backup card is charged instead. *** ## What You Can Do | Action | Mutation | Description | | ---------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | | [Add Bank Card](/features/add-bank-card) | `addBankCard` | Add a debit, credit, or prepaid card to a user's account with a billing address. | | [Update Bank Card](/features/add-bank-card) | `updateBankCardNickname` · `updateBankCardPreferredMerchantCategoryCode` | Rename a card or set its preferred merchant category code (MCC). | | [Delete Bank Card](/features/delete-bank-card) | `deleteBankCard` | Remove a card by setting its status to `INACTIVE`. | All bank card mutations require the `MANAGE_PAYMENT` scope. *** ## Fees & CashbackCard type affects the effective cashback rate a user earns on a purchase: | Card Type | Processing Fee | Relative Cashback | | ----------- | -------------- | ----------------- | | **Debit** | –1% | Higher | | **Credit** | –3% | Lower | | **Prepaid** | Varies by card | Varies | **Debit cards earn more cashback than credit cards.** Bank accounts (ACH) carry no processing fees and earn the full merchant cashback rate. Where a user has the choice, ACH earns the most, debit is next, and credit carries the highest fee. See [Bank Accounts](/features/link-external-bank-accounts) for the fee-free option. *** ## Requirements**ope.** Every bank card mutation requires the `MANAGE_PAYMENT` scope on the user access token. **Billing address.** Each card must be associated with an address. Provide either a new `billingAddress` object or an existing `userAddressId` — not both. Use the `getUserAddresses` query to look up a stored `userAddressId`. **Only personal applications can add bank cards via the API.** Publicly available applications that want to add bank cards will need to complete PCI certification and an attestation of PCI compliance. If your application is not PCI-certified, direct users to add bank cards through the Fluz app or web portal. *** > Setting a Primary, Preferred, or Backup Card, updating, and deleting cards is available via the API. **Choosing which funding source is primary, preferred, or backup is managed in the Fluz dashboard**, not through the API. *** ## Reading Bank Cardse `getWallet` query to return all bank cards on a user's account, along with their type, last four digits, and current status. ```graphql theme={null} query getWallet { getWallet { bankCards { bankCardId cardType lastFourDigits cardStatus } } } ``` See [View Funding Sources](/features/view-funding-sources) for the full field reference. *** **Want to learn more?** Speak with our experts for more info or to request a demo. # Linking Bank Account via Plaid Source: https://docs.fluz.app/features/link-via-plaid Use these operations to let a user connect a bank account to Fluz through **Plaid Link**. Linking is the entry point for all bank-account funding: once an account is connected, you can read its balance and spend power, pull its transaction history, and repair the connection if it later drops — each covered on its own page. Transactional Graph Service (TGS) exposes a public GraphQL wrapper over the Fluz Plaid integration. Identity-service remains the system of record for Plaid tokens, bank-account ingestion, balances, historical transactions, and Plaid webhooks. TGS never returns Plaid access tokens or public tokens to your app. ## Auth Call `/api/v1/graphql` with a Fluz **user Bearer access token** that includes the `MANAGE_PAYMENT` scope. The Plaid fields do **not** allow Basic auth. Obtain a user access token through the standard TGS auth flow first, then call these fields with: ```http theme={null} Authorization: Bearer ``` ## The link flow at a glance 1. Create a Link token (`createPlaidLinkToken`). 2. Open Plaid Link with that token. 3. On Plaid's `onSuccess`, send the returned `public_token` back to TGS (`completePlaidLink`). 4. Store the `platformItemId` from the completion response — it's the safe public identifier you'll use later to relink. ### 1. Create a Link token ```graphql theme={null} mutation CreatePlaidLinkToken($input: CreatePlaidLinkTokenInput!) { createPlaidLinkToken(input: $input) { linkToken expiration requestId mode } } ``` ```json theme={null} { "input": {} } ``` For **native** Link, pass `deviceOs` as `IOS` or `ANDROID` so identity-service includes the correct Plaid OAuth redirect option. ```json theme={null} { "input": { "deviceOs": "IOS" } } ``` ### 2. Open Plaid Link Initialize Plaid Link with the returned `linkToken`. (See the Web SDK example below.) ### 3. Complete the link In Plaid Link's `onSuccess`, send the returned `public_token` to TGS. ```graphql theme={null} mutation CompletePlaidLink($input: CompletePlaidLinkInput!) { completePlaidLink(input: $input) { requiresAddress bankAccountId bankInstitutionAuthId newlyLinkedBankInstitutionAuthId bankInstitutionName platformItemId bankAccounts { bankInstitutionAuthId bankAccountId bankName lastFour type subtype } } } ``` ```json theme={null} { "input": { "publicToken": "public-sandbox-..." } } ``` ### 4. Store the `platformItemId` Save `platformItemId` from the completion response. This is the safe public identifier used later to relink a disconnected connection. If it isn't returned, call `getPlaidBankAccounts` for the user and store the persisted `platformItemId` from that response. If `completePlaidLink` returns `requiresAddress: true`, attach an address before the account is usable — see **Attach an address** below. ## Plaid Web SDK example Plaid's Web SDK script must be loaded directly from Plaid's CDN. ```html theme={null} ``` Create a Link token through TGS, initialize Plaid Link with that token, and pass Plaid's `public_token` back to TGS in `onSuccess`. Calling `startPlaidLink()` with no argument begins a **new** link; calling `startPlaidLink(existingPlatformItemId)` begins a **relink** (see *Relinking Disconnected Bank Accounts*). ```javascript theme={null} const FLUZ_GRAPHQL_URL = '/api/v1/graphql'; const fluzUserAccessToken = ''; const fluzGraphql = async (query, variables) => { const response = await fetch(FLUZ_GRAPHQL_URL, { method: 'POST', headers: { Authorization: `Bearer ${fluzUserAccessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ query, variables }), }); const body = await response.json(); if (!response.ok || body.errors?.length) { throw new Error(body.errors?.[0]?.message ?? 'Fluz GraphQL request failed'); } return body.data; }; const createPlaidLinkToken = async (platformItemId) => { const data = await fluzGraphql( ` mutation CreatePlaidLinkToken($input: CreatePlaidLinkTokenInput!) { createPlaidLinkToken(input: $input) { linkToken mode } } `, { input: { platformItemId } }, ); return data.createPlaidLinkToken; }; const completePlaidLink = async (publicToken, platformItemId) => { const data = await fluzGraphql( ` mutation CompletePlaidLink($input: CompletePlaidLinkInput!) { completePlaidLink(input: $input) { requiresAddress bankInstitutionAuthId newlyLinkedBankInstitutionAuthId bankAccountId platformItemId bankAccounts { bankInstitutionAuthId bankAccountId bankName lastFour type subtype } } } `, { input: { publicToken, platformItemId } }, ); return data.completePlaidLink; }; const getPlaidBankAccounts = async (input) => { const data = await fluzGraphql( ` query GetPlaidBankAccounts($input: PlaidBankAccountFilterInput) { getPlaidBankAccounts(input: $input) { platformItemId bankInstitutionAuthId bankAccountId bankInstitutionName lastFour type subtype } } `, { input }, ); return data.getPlaidBankAccounts; }; const resolvePlatformItemId = async (completion) => { if (completion.platformItemId) return completion.platformItemId; const bankInstitutionAuthId = completion.bankInstitutionAuthId ?? completion.newlyLinkedBankInstitutionAuthId; const accounts = await getPlaidBankAccounts({ bankInstitutionAuthId }); return accounts[0]?.platformItemId; }; const startPlaidLink = async (platformItemId) => { const { linkToken } = await createPlaidLinkToken(platformItemId); const handler = window.Plaid.create({ token: linkToken, onSuccess: async (publicToken, metadata) => { const result = await completePlaidLink(publicToken, platformItemId); const persistedPlatformItemId = await resolvePlatformItemId(result); await saveConnectionForRelinkLater({ platformItemId: persistedPlatformItemId, bankInstitutionAuthId: result.bankInstitutionAuthId ?? result.newlyLinkedBankInstitutionAuthId, bankAccounts: result.bankAccounts, plaidLinkSessionId: metadata.link_session_id, }); }, onExit: (error, metadata) => { console.log('Plaid Link exited', { error, metadata }); }, }); handler.open(); }; ``` ## List linked accounts Return the safe Plaid bank accounts for the authenticated user. ```graphql theme={null} query GetPlaidBankAccounts($input: PlaidBankAccountFilterInput) { getPlaidBankAccounts(input: $input) { platformItemId bankInstitutionAuthId bankInstitutionName bankAccountId accountName lastFour type subtype status } } ``` ```json theme={null} { "input": { "platformItemId": "plaid-item-id" } } ``` ## Attach an address If `completePlaidLink` returned `requiresAddress: true`, attach an address to the linked bank institution. ```graphql theme={null} mutation CreatePlaidLinkAddress($input: CreatePlaidLinkAddressInput!) { createPlaidLinkAddress(input: $input) { addressId } } ``` ```json theme={null} { "input": { "bankInstitutionAuthId": "bank-institution-auth-id", "address": { "streetAddressLine1": "123 Main St", "streetAddressLine2": "Apt 4", "city": "New York", "state": "NY", "postalCode": "10001", "country": "US" } } } ``` ## Remove a bank connection Remove a Plaid bank institution from the user's account. ```graphql theme={null} mutation RemovePlaidBankInstitution($input: RemovePlaidBankInstitutionInput!) { removePlaidBankInstitution(input: $input) { removed bankInstitutionAuthId } } ``` ```json theme={null} { "input": { "bankInstitutionAuthId": "bank-institution-auth-id", "reason": "USER_REQUESTED" } } ``` ## Security boundaries TGS never returns Plaid access tokens, Plaid public tokens, identity-match details, bank account numbers, or routing numbers. Bank account names and nicknames may be user-entered banking labels and are returned only as account metadata. Identity-service remains responsible for: * Plaid token exchange * Plaid access token storage * Bank institution and bank account creation or repair * Balance fetching * Historical transaction ingestion * Bank institution removal * Plaid webhooks TGS does not expose identity-service directly and does not add a public Plaid webhook route. ## Unsupported flows Manual Plaid micro-deposit linking is intentionally unsupported by this public wrapper. New links and relinks must use the normal Plaid Link flow, which supports balances and historical transactions. If a user starts a new link for the same institution instead of choosing relink, complete it as a normal new link. Identity-service owns bank-account dedupe and repair; store the returned `platformItemId` after completion. ## Error handling If `completePlaidLink` times out or fails after Plaid returned a `public_token`, first call `getPlaidBankAccounts` using the stored or expected `platformItemId` before retrying. Retry only if Plaid returns a fresh `public_token` from a new Link session — Plaid public tokens are short-lived and single-use.
# Lock Virtual Card Source: https://docs.fluz.app/features/lock-virtual-card The `lockVirtualCard` mutation allows you to lock an existing virtual card, preventing further transactions. **Prerequisites:** a user access token with the `EDIT_VIRTUALCARD` scope, and the `virtualCardId` of the card to lock. ## Arguments * **`input`** (`LockVirtualCardInput!`): The input object containing the ID of the virtual card to lock. ## LockVirtualCardInput fields | Field | Type | Description | Required | | :-------------- | :------ | :------------------------ | :------- | | `virtualCardId` | `UUID!` | The virtual card to lock. | Yes | ## Sample mutation ```graphql theme={null} mutation { lockVirtualCard(input: { virtualCardId: "07df5653-43a8-4532-9881-3ab5857bbe11" }) { virtualCardId locked } } ``` ## cURL example ```curl theme={null} curl -X POST \ https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \ -d '{ "query": "mutation { lockVirtualCard(input: { virtualCardId: \"07df5653-43a8-4532-9881-3ab5857bbe11\" }) { virtualCardId locked } }" }' ``` ## Sample response ```json theme={null} { "data": { "lockVirtualCard": { "virtualCardId": "07df5653-43a8-4532-9881-3ab5857bbe11", "locked": true } } } ``` ## Response fields | Field | Type | Description | | --------------- | --------- | ------------------------------------------------------------------------ | | `virtualCardId` | `UUID` | The unique identifier of the virtual card that was targeted for locking. | | `locked` | `Boolean` | `true` if the card was successfully locked, `false` otherwise. | To lock a card automatically after its first successful authorization, set `lockCardNextUse: true` at creation instead — see [Create Virtual Card](/features/create-card). ## Next steps Re-enable a locked card so authorizations succeed again. # Managing Spend Power Source: https://docs.fluz.app/features/manage-spend-power Once a bank account is linked (see *Linking a Plaid Bank Account*), you can read its balance and spend power and, when you need a fresher reading, trigger a realtime balance refresh. ## What spend power means **Spend power** is how much a user can currently spend from a linked bank account. It is based on the account's most recent known balance, reduced by recent ACH outflows that have not fully settled yet. When a user spends from the account, those amounts are held for roughly three business days before they clear and stop counting against spend power. (The exact clearing window is set per bank and can be shorter.) Fluz keeps the stored balance for each linked account up to date automatically. When you need a more current reading — for example, right after a user moves money in or out of their bank — request a **realtime refresh** (rate limited; see below). Refreshing the balance updates the balance-derived spend-power figure. ## Auth Call `/api/v1/graphql` with a Fluz user Bearer access token that includes `MANAGE_PAYMENT`. Basic auth is not allowed on these fields. ```http theme={null} Authorization: Bearer ``` ## Get spend power for one bank account ```graphql theme={null} query GetPlaidBankAccountSpendPower($input: PlaidBankAccountInput!) { getPlaidBankAccountSpendPower(input: $input) { bankAccountId spendPower availableSpendPower lastRecordedBalance pendingTransactions updatedAt } } ``` ```json theme={null} { "input": { "bankAccountId": "bank-account-id" } } ``` **Response fields** | Field | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `lastRecordedBalance` | The most recent known bank balance for the account. | | `pendingTransactions` | Recent ACH outflows (debits from roughly the last three business days) that have not yet settled and are still being held against the balance. | | `availableSpendPower` | What the user can spend right now, derived from the latest balance less pending. Moves immediately after a realtime balance refresh. | | `spendPower` | The account's decisioned spend-power figure. May update on a slower cadence than the balance, so it can briefly differ from `availableSpendPower` after a refresh. | | `updatedAt` | When the figure was last computed. | ## Get the latest stored balance For one bank account: ```graphql theme={null} query GetPlaidBankAccountBalance($input: PlaidBankAccountInput!) { getPlaidBankAccountBalance(input: $input) { platformItemId bankInstitutionAuthId bankAccountId amount current source trigger startedAt } } ``` ```json theme={null} { "input": { "bankAccountId": "bank-account-id" } } ``` For all of the user's connected accounts: ```graphql theme={null} query GetPlaidBankBalances($input: PlaidBankBalanceFilterInput) { getPlaidBankBalances(input: $input) { platformItemId bankInstitutionAuthId bankAccountId amount current source trigger startedAt } } ``` ## Refresh one account in realtime Request a rate-limited realtime balance refresh for a single bank account. This pulls a fresh balance from Plaid and updates the balance-derived spend power. ```graphql theme={null} mutation RefreshPlaidBankAccountBalance($input: PlaidBankAccountInput!) { refreshPlaidBankAccountBalance(input: $input) { status balances { bankAccountId amount current source trigger startedAt } } } ``` ```json theme={null} { "input": { "bankAccountId": "bank-account-id" } } ``` ### Refresh rate limit To control cost, realtime refreshes are rate limited **per Plaid institution**: one realtime refresh per hour and six realtime refreshes per day. A refresh requested inside a limit window does not call Plaid again — it returns the latest stored balance instead, so the call still succeeds and returns data. Design your UI so that a returned balance is not always assumed to be a brand-new realtime reading. ## Refresh all connections (cached) Request a cached refresh of all connected Plaid bank data for the authenticated account. ```graphql theme={null} mutation RefreshPlaidBankConnections { refreshPlaidBankConnections { status balances { bankAccountId amount current } verifyMembers { platformItemId bankInstitutionAuthId } } } ``` Use `refreshPlaidBankConnections` when you want identity-service to perform its cached all-connection refresh. > If `verifyMembers` is returned, one or more connections need repair. Use the `platformItemId` to start the relink flow — see **Relinking Disconnected Bank Accounts**. Note that the single-account `refreshPlaidBankAccountBalance` mutation does **not** return this signal, so use `refreshPlaidBankConnections` when you need to detect disconnected connections.
# Overview Source: https://docs.fluz.app/features/move-funds-with-external-accounts Move money between a user's **Fluz wallet** and their **external accounts** — a bank account, bank card, or digital wallet (PayPal, Venmo). Deposits bring money *into* the Fluz wallet from an external source to fund purchases; withdrawals send money *out* of the Fluz wallet to a linked external destination. This differs from moving money *within* Fluz: to shift funds between a user's own spend accounts use [Transfer Funds Between Spend Accounts](/features/transfer-between-spend-accounts), and to send funds to a different Fluz account use [Account to Account Transfers](/features/account-to-account-transfers). | Direction | Page | What it does | | --------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------ | | **In** | [Deposit Funds From External Accounts](/features/deposit-from-external-accounts) | Add money to a Fluz balance from a linked funding source. | | **In** | [Redeem Fluz Gift Card to Gift Card Balance](/features/redeem-fluz-gift-card) | Credit a Fluz Gift Card code to the gift card balance. | | **Out** | [Withdraw Funds To External Account](/features/withdraw-to-external-account) | Send money from a Fluz balance to a linked external account. | *** ## Fluz Balances Deposits and withdrawals interact with a few distinct balances. Knowing which balance a movement touches is important, because not every balance can be withdrawn. | Balance | Funded by | Withdrawable | | --------------------- | ------------------------------------------------------------ | ------------------- | | **Cash balance** | Deposits from a funding source | ✓ | | **Rewards balance** | Cashback earned on purchases | ✓ | | **Gift card balance** | Deposits to `GIFT_CARD_BALANCE` and redeemed Fluz Gift Cards | ✗ (spend only) | | **Reserve balance** | Deposits to `RESERVE_BALANCE` | ✗ (held in reserve) | **The gift card balance is spend-only.** Funds credited to the gift card balance — whether by depositing to `GIFT_CARD_BALANCE` or redeeming a Fluz Gift Card — can be used for purchases but cannot be withdrawn to an external account. *** ## Depositing Funds Use the `depositCashBalance` mutation to move money into Fluz from a linked funding source. You choose the **destination balance** (`CASH_BALANCE`, `GIFT_CARD_BALANCE`, or `RESERVE_BALANCE`) and the **funding source** — a `bankAccountId`, `bankCardId`, or `paypalVaultId` retrieved from `getWallet`. When depositing to a cash balance, you can target a specific spend account with `userCashBalanceId`. Deposits may settle instantly or within 2–5 business days depending on the funding source. Requires the `MAKE_DEPOSIT` scope. See [Deposit Funds From External Accounts](/features/deposit-from-external-accounts) for the full input and response reference. *** ## Redeeming a Fluz Gift Card Fluz Gift Card is a special kind of deposit. The `redeemFluzGiftCard` mutation credits a gift card **code** directly to the user's gift card balance (`giftCardCashBalance`). Redemptions are instant, and any activation fee is reflected in `depositFee`. Requires the `MAKE_DEPOSIT` scope. See [Redeem Fluz Gift Card to Gift Card Balance](/features/redeem-fluz-gift-card) for details. *** ## Withdrawing Funds Use the `withdrawCashBalance` mutation to send money out of Fluz to a linked external account. You choose the **source balance** (`CASH_BALANCE` or `REWARDS_BALANCE`) and a **withdrawal method**, providing the matching destination ID: | Method | Required field | Notes | | ----------- | ---------------- | --------------------------------------------------------------------------------------- | | `BANK_ACH` | `bankAccountId` | ACH transfer to a linked bank account. Typically settles in 1–3 business days. No fees. | | `BANK_CARD` | `bankCardId` | Push-to-card transfer to an eligible linked debit card. May have fees. | | `PAYPAL` | `paypalVaultId` | Transfer to a linked PayPal account. May have fees. | | `VENMO` | `venmoAccountId` | Transfer to a linked Venmo account. May have fees. | When withdrawing from a cash balance, specify which spend account to draw from with `cashBalanceId`. Withdrawals require the `MAKE_WITHDRAWAL` scope. See [Withdraw Funds To External Account](/features/withdraw-to-external-account) for the full input reference, error handling, and multi-seat behavior. *** ## Idempotency Deposit, redemption, and withdrawal must include a unique, client-generated `idempotencyKey`. If the same key is submitted more than once, the API returns the result of the original request instead of processing it again — preventing duplicate money movement. Always generate a fresh UUID per request. *** ## Requirements at a Glance | tion | Mutation | Scope | | ----------------------- | --------------------- | ----------------- | | Deposit funds | `depositCashBalance` | `MAKE_DEPOSIT` | | Redeem a Fluz Gift Card | `redeemFluzGiftCard` | `MAKE_DEPOSIT` | | Withdraw funds | `withdrawCashBalance` | `MAKE_WITHDRAWAL` | Before moving funds, use [Check Account Balance](/check-account-balance) or `getWallet` to confirm the user has sufficient available balance and to retrieve the relevant funding source IDs. *** **Want to learn more?** Speak with our experts for more info or to request a demo. # Delivery Methods Source: https://docs.fluz.app/features/open-loop-cards/open-loop-card-delivery-methods Four ways to get an open-loop card into a recipient's hands — and how much you need to know about them to do it. **The short version: you do not need to know anything about your recipient to generate a card.** Recipient contact details are only required when you want *Fluz* to do the delivering. If you deliver the link yourself, you send us a card limit, an offer, and a funding account — nothing else. The recipient fills in their own details on the hosted page. ## Pick your method If you deliver it yourself — through your own email, your own SMS, in-app, in a portal, or by handing the URL to a downstream client — use **`GENERATE_URL`**. No recipient data required. Email (`EMAIL`) or SMS (`PHONE_NUMBER`). You supply one address or number per card. If you do — and you want the recipient to skip data entry entirely — ask your Fluz rep about **pre-filled enrollment**. This is a gated option, not part of the standard `generateVCShareLinks` flow. ## The four options at a glance | # | Option | `shareMethod` | What you send Fluz | Who delivers the link | What the recipient enters | | - | ------------------------- | ------------------------ | ------------------------------- | --------------------- | ------------------------- | | 1 | **Generate a link** | `GENERATE_URL` | Nothing about the recipient | **You** | Their own details | | 2 | **Fluz emails it** | `EMAIL` | One email address per card | Fluz | Their own details | | 3 | **Fluz texts it** | `PHONE_NUMBER` | One phone number per card | Fluz | Their own details | | 4 | **Pre-filled enrollment** | Gated — contact your rep | Full recipient identity details | You or Fluz | Nothing — they just claim | Options 1–3 are three values of the same `shareMethod` field on a single mutation. Switching between them is a one-line change — you are not integrating three different APIs. ## Option 1 — Generate a link, you deliver it **Most partners want this one.** You call the API, you get back an array of URLs, you do whatever you want with them: email them from your own system, text them, drop them into a customer portal, or hand them to a downstream client who distributes them to their own end users. Fluz sends **nothing** to anyone. We have no recipient contact details on file for these links, because you never gave us any. ```json Generate URLs theme={null} { "input": { "cardLimit": 100, "offerId": "7c4a1d92-3fb8-4e05-9a61-2d8ef50b7c33", "quantity": 1, "daysUntilExpiration": 30, "shareMethod": "GENERATE_URL", "userCashBalanceId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" } } ``` ```json Response theme={null} { "data": { "generateVCShareLinks": { "shareLinks": [ "https://fluz.app/virtual-prepaid-card/3f8a...c1" ] } } } ``` With `GENERATE_URL`, both `recipientListEmail` and `recipientListPhone` must be **empty or omitted**. Sending a recipient list alongside `GENERATE_URL` is a validation error and creates no records. Set `quantity` above 1 to mint a batch in a single call. You get one distinct URL per unit, and each URL is claimable exactly once. Distribute the URLs exactly as returned — do not rewrite or re-shorten them. ## Option 2 — Fluz emails the link You supply one email address per card and Fluz sends the email. The recipient clicks through to the same hosted page as in Option 1 and enters their own details there. Use this when you already hold recipient emails and would rather not build delivery yourself. ```json Email theme={null} { "input": { "cardLimit": 100, "offerId": "7c4a1d92-3fb8-4e05-9a61-2d8ef50b7c33", "quantity": 2, "daysUntilExpiration": 30, "shareMethod": "EMAIL", "recipientListEmail": ["mike.bennett@example.com", "dana.ruiz@example.com"], "userCashBalanceId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" } } ``` `recipientListEmail` length **must equal** `quantity`. A mismatch returns a validation error and creates no records — no partial batches. ## Option 3 — Fluz texts the link Same as Option 2, over SMS. You supply one phone number per card in E.164 format. Fluz sends the SMS; Fluz does **not** also send an email on this path. ```json SMS theme={null} { "input": { "cardLimit": 100, "offerId": "7c4a1d92-3fb8-4e05-9a61-2d8ef50b7c33", "quantity": 2, "daysUntilExpiration": 30, "shareMethod": "PHONE_NUMBER", "recipientListPhone": ["+12125550101", "+12125550102"], "userCashBalanceId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" } } ``` `recipientListPhone` length must equal `quantity`, and both lists are mutually exclusive — send the one that matches your `shareMethod` and leave the other empty. ## Option 4 — Pre-filled enrollment Some partners want a white-glove experience where the recipient enters nothing at all. In that model you pass Fluz the identity details the card enrollment needs, and the recipient's only action is opening the link and claiming the card. This is a **gated option** and is not part of the standard `generateVCShareLinks` input. If your program needs it, talk to your Fluz account team — it carries additional data-handling and compliance obligations on your side, since you are supplying personal information on behalf of a person who has not interacted with Fluz yet. Choose this only if you genuinely hold verified recipient identity data. If you are tempted toward Option 4 simply to avoid asking recipients for information, Option 1 is almost certainly what you want instead. ## What the recipient does Identical across Options 1–3. The link is the same hosted page no matter who delivered it: No app download, no Fluz account, no password. A one-time code confirms the person holding the link. The recipient supplies the details the card needs. No PIN prompt occurs at this stage. The card is funded from your spend account **at claim time**, not when the link was generated. The recipient becomes an authorized user of that one card object — nothing else on your account. The card is not auto-revealed on claim: revealing it prompts the recipient to enter their PIN, or create one if they haven't set one yet. See [Recipient Experience](/features/open-loop-cards/open-loop-cards-recipient-experience) for the full walkthrough and the states a recipient sees when a link is expired, revoked, or already claimed. ## Common points of confusion No. That is a common misreading of the API reference. Recipient fields exist so that **Fluz can deliver on your behalf** — they are not inputs to card issuance. With `GENERATE_URL` you send no recipient data at all. No. `recipientListEmail` is required **only** when `shareMethod = EMAIL`. With `GENERATE_URL` and `PHONE_NUMBER` it must be empty. Yes — that is exactly the `GENERATE_URL` pattern. The URL is bearer-style: whoever opens it first and completes verification claims the card. Treat links as sensitive and deliver them over a channel you trust. Not today. Link generation is API-only. Test against the staging environment with a staging token carrying the `CREATE_SHARE_LINK` scope — see [Staging vs. Live Environment](/docs/staging-vs-live-environment). Yes. `shareMethod` is set per call, not per account. Nothing stops you from generating URLs for one batch and having Fluz email the next. ## Requirements common to all methods | Requirement | Detail | | ----------- | ---------------------------------------------------------------------------------------------------------------------------- | | Auth | Bearer access token with the `CREATE_SHARE_LINK` scope. Basic auth is rejected. | | Funding | `userCashBalanceId` — a spend account on your own account. Effectively required despite being marked optional in the schema. | | Offer | `offerId` must be an active offer whose merchant is shareable. | | Expiration | `daysUntilExpiration` defaults to 30. This date is also the card's freeze date. | ## Next steps Full operation reference for generating, listing, and deactivating links. What your recipients see, and the rules that govern their card. # Recipient Experience Source: https://docs.fluz.app/features/open-loop-cards/open-loop-cards-recipient-experience What a recipient sees when they open a hosted virtual card link — sign-in, two-factor verification, PIN, claim, and the program rules that govern how the card can be spent. Every hosted link generated with [`generateVCShareLinks`](/features/open-loop-cards/send-open-loop-cards#generatevcsharelinks) resolves to a Fluz-hosted activation page at `https://fluz.app/virtual-prepaid-card/{share_request_id}`. The recipient claims their card there — no app download, no password. The recipient becomes an authorized user of that virtual card object only. They do not gain access to your account, your balances, or any other cards. See it in action: [desktop flow](https://test.fluz.app/wp-content/uploads/2026/04/Web-Share-V6.mp4) · [mobile flow](https://test.fluz.app/wp-content/uploads/2026/04/Mob-Share-F.mp4). [Demo open loop cards](https://demos.fluz.app/card-issuing/?launch-card=1) ## Claim flow The recipient sees the activation page branded with the sender's business. They sign in through the Fluz auth portal (new recipients onboard here). On initial load, existing users are taken to the 2FA screen. 2FA is required before the card can be viewed or claimed. If the recipient has no billing address on file, they're prompted to add one. *Billing address is required for online purchases.* A single-load virtual card is created and assigned to the recipient, funded from the sender's account, with a lock date equal to the link's expiration date. No PIN prompt occurs during activation. The card is not auto-revealed on claim. When the recipient chooses to reveal card details, they're prompted to enter their PIN — or create one, if they haven't set one yet. Once revealed, the recipient can see card details, transactions, spend online, and (where supported) add the card to Apple Pay or Google Pay in one tap. **Already claimed?** If the same user opens a link they already claimed, they land on their card and are prompted for their PIN to reveal it. If a *different* user opens a link that someone else already claimed, they're shown an access-denied state after 2FA. **Funding timing.** The card limit is drawn against the sender's spend account **at claim time**, not when the link is generated. ## What the recipient sees for expiration The link's expiration date — set by the sender via `daysUntilExpiration`, defaulting to 30 days — is surfaced to the recipient, typically as a "Valid until" date. * Before claim, that date is the **last day the link can be claimed**. * After claim, that date is the card's **freeze / lock date** (end of day). After it, the card can no longer be spent. * The printed **card expiry** is aligned to the end of that month (e.g., a freeze date of 6/15/2026 yields a card expiry of 6/30/2026). ## Recipient-facing link errors | Condition | What the recipient sees | | ---------------------------------------------- | -------------------------------------------------------- | | Link expired before being claimed | "Expired before issued" — link can no longer be claimed. | | Sender deactivated the link early | "Frozen by sender" — link was revoked before expiry. | | Card was claimed, then its lock date passed | "Expired after issued" — card is frozen. | | A different user opens an already-claimed link | "Access denied". | ## Program rules to communicate to recipients These are program-level rules for hosted (open-loop) virtual cards. Confirm the exact values for **your** program with your Fluz representative — several are partner-negotiated. | Rule | Default | Notes | | --------------------------------- | ---------------------- | -------------------------------------------------------------------------------- | | Daily account spend limit | **\$250,000/day** | Some partners have a custom limit. | | Restaurant transaction buffer | **25%** | A 25% buffer applies to all programs (to accommodate tips/holds at restaurants). | | Restricted merchants / categories | Program-specific | Certain merchant categories are restricted. | | Funding | Sender's spend account | Cards are funded from the sender's account at claim time. | **Customer support for recipients:** 1-888-360-6660 · [humans@fluz.app](mailto:humans@fluz.app) The full partner-facing reference (terminology, recipient walkthrough with screenshots, funding instructions, restricted categories, and support) lives in the **Partner Guide — Hosted URL Virtual Cards**. Ask your Fluz contact for the latest copy for your program. ## Next steps Generate, list, and deactivate hosted virtual card links from the API. Issue many cards at once for programmatic distribution. # Register & Send Source: https://docs.fluz.app/features/open-loop-cards/register-and-send Register a recipient's identity and billing address up front with registerUser, then generate a hosted virtual card link bound to that known user with generateVCShareLinks (shareMethod: EXISTING_USER). Unlike the standard flow, the card is created at link-generation time, not at claim. The [standard hosted-link flow](/features/open-loop-cards/send-open-loop-cards) defers everything to the recipient: you mint a link, and Fluz doesn't create the virtual card until the recipient opens it, verifies themselves, and claims it. This flow inverts that for recipients you've already identified. You register the recipient's identity and billing address yourself with `registerUser`, then generate a share link with `generateVCShareLinks` using `shareMethod: EXISTING_USER`. Fluz creates the virtual card **immediately**, at generation time — not at claim — and binds it to that one recipient. The link is still delivered and claimed the normal way; only card creation moves earlier. Funding is still drawn at claim time, same as the standard flow — from `userCashBalanceId`, falling back to your prepayment or rewards balance if enabled and the spend account runs short. **When to use this instead of a plain share link** * You already know exactly who the recipient is (by user ID) and want the card created and ready before you notify them, rather than waiting on them to claim it. * You want a hard guarantee that only the intended recipient can ever view the link — not "first person to click it." * You're sending to a batch of known recipients and want deterministic 1:1 mapping between recipient and card. ## Before you start You'll need a Bearer access token. Basic auth is not accepted for either operation. | Operation | Scope | Also required | | ---------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------- | | `registerUser` | — | Registration permission enabled on your application | | `generateVCShareLinks` | `CREATE_SHARE_LINK` | An active virtual card offer, a spend account to fund from (optionally with prepayment/rewards balance as fallback) | **User registration is not enabled by default.** `registerUser` is a restricted mutation — your application must be explicitly approved by Fluz before it can create users. **Contact your Fluz sales rep or account manager to have it enabled.** Calls from an unapproved application fail with `AUTH-0022`. If the recipient already has a Fluz account, you can skip registration and go straight to `generateVCShareLinks` with their existing `recipientUserIds`. See [Authentication](/concepts/authentication) for how to mint a scoped token. [Demo open loop cards](https://demos.fluz.app/card-issuing/?launch-card=1) ## How this differs from the standard flow | | Standard (`GENERATE_URL` / `EMAIL` / `PHONE_NUMBER`) | This flow (`EXISTING_USER`) | | ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------- | | Recipient identified by | Email / phone in `recipientListEmail` / `recipientListPhone` | Known Fluz `userId` in `recipientUserIds` | | Card created | At claim, when the recipient completes onboarding | At `generateVCShareLinks` call time | | Card funded | At claim time | At claim time (same as standard) | | Who can claim the link | Whoever opens it first, if unclaimed | Only the assigned recipient — enforced from the moment the link is created | | Recipient onboarding at claim | Sign-in, 2FA, billing address (if needed) | Sign-in, 2FA — no billing address prompt, since it was collected at registration | | Revealing the card | Prompts for PIN (or PIN creation, if none set yet) | Prompts for PIN (or PIN creation, if none set yet) | See [Send Open Loop Cards](/features/open-loop-cards/send-open-loop-cards) for the standard flow and [Recipient Experience](/features/open-loop-cards/open-loop-cards-recipient-experience) for the full claim walkthrough. ## Notes and limitations * **`recipientUserIds` must reference existing Fluz users.** If the recipient isn't registered yet, register them first with `registerUser` (this flow), or use the standard hosted-link flow and let Fluz onboard them at claim time. * **`recipientUserIds` length must equal `quantity`.** A mismatch returns a validation error and creates no records. * **Card creation moves to generation time — funding does not.** The virtual card object and recipient binding are created when you call `generateVCShareLinks`, but funds are still drawn at claim time, same as the standard flow: from `userCashBalanceId` first, then your prepayment or rewards balance as a fallback if `usePrepaymentBalance` / `useRewardsBalance` are set and the spend account is insufficient. * **Registration is per-application and per-environment.** Permission granted for staging does not carry to production. See [Deploying to Production](/deploying-to-production). * **Never register real people in staging.** See [Staging vs. Live](/concepts/environments). ## Next steps The standard flow — generate links and let Fluz issue the card at claim time. What the recipient sees when they open and claim a hosted link. Full reference for `registerUser`, including error handling and fallback patterns. # Open Loop Cards Overview Source: https://docs.fluz.app/features/open-loop-cards/send-open-loop-cards Generate hosted virtual card links ("open-loop" Send Cards) from your platform. You call one API to mint one or more links, then deliver those links to recipients however you like — by email, by SMS, or by handing back the raw URLs to embed in your own flows. When a recipient opens the link, they land on a Fluz-hosted page, verify themselves, and claim a single-load virtual card funded from your account. **Prerequisites:** a Bearer access token carrying the `CREATE_SHARE_LINK` scope. Basic auth is rejected. Contact your sales rep to enable access. See [Authentication](/concepts/authentication). **What "hosted" / "open-loop" means.** A *hosted* link points to a Fluz-hosted activation page. *Open-loop* means the resulting virtual card is a network card (Visa/Mastercard-style) that can be spent at many merchants, subject to your program's rules — not a single-brand closed-loop gift card. [Demo open loop cards](https://demos.fluz.app/card-issuing/?launch-card=1) ## How it works Call `generateVCShareLinks` with the offer, card limit, quantity, funding source, and a delivery method. Each link represents one card with its own limit, funded from the spend account you specify. Each link maps to one share request (`PENDING`) and one hosted URL. With `GENERATE_URL` you get the URLs back to distribute yourself. With `EMAIL` or `PHONE_NUMBER`, Fluz delivers a link to each recipient for you. The recipient opens the link and verifies their phone number with a one-time code — no app download, no password. The card limit is drawn against your spend account at claim time, not when the link is generated. The card isn't auto-revealed on claim; revealing it prompts the recipient to enter their PIN, or create one if they haven't set one yet. See [Recipient Experience](/features/open-loop-cards/open-loop-cards-recipient-experience) for the full walkthrough. The recipient becomes an authorized user of that virtual card object only — they do not gain access to your account, balances, or any other cards. ![Send cards flow diagram](https://files.readme.io/c19029bfeaca196f7eadc2f020ab56f2aad893261f561307bec543e700f0d74b-diagram.svg) ## Availability & scope | Capability | Status | | ----------------------------- | ----------------------------------- | | Single-load virtual cards | ✅ Supported | | Single-use virtual cards | ✅ Supported | | Reloadable cards | ❌ Not supported | | Generate links via API | ✅ Supported | | Generate links via CSV Import | ❌ Coming Soon | | Hosted **gift-card** links | ❌ Not in scope (virtual cards only) | The card share-link object type is `VIRTUAL_CARD` and the card type is `SINGLE_LOAD`. **Gift cards:** Despite the "virtual cards **and** gift cards" framing of the broader initiative, there is no hosted gift-card claim flow today. Gift-card balances appear in this area only as a *potential funding source* for hosted virtual cards (planned, not yet enabled). Document and build against virtual cards only. ## Operation reference There are three public operations, all gated by the `CREATE_SHARE_LINK` scope: | Operation | Type | Purpose | | ------------------------ | -------- | -------------------------------------------- | | `generateVCShareLinks` | Mutation | Create one or more hosted virtual card links | | `getVCShareLinks` | Query | List/inspect previously generated links | | `deactivateVCShareLinks` | Mutation | Deactivate (expire) links you generated | All Send Cards operations are on the Fluz GraphQL API at `POST https:///api/v1/graphql` with an `Authorization: Bearer ` header. The token must carry the `CREATE_SHARE_LINK` scope — without it, every operation returns *"Missing permissions! Please contact your sales rep to get access to generate VC share links."* ## generateVCShareLinks Creates `quantity` share requests and returns one hosted link per request. ```graphql theme={null} mutation GenerateVCShareLinks($input: GenerateVCShareLinksInput!) { generateVCShareLinks(input: $input) { shareLinks } } ``` ### Input fields | Field | Type | Required | Description | | ------------------------ | --------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `cardLimit` | `Int!` | Yes | Spend limit (and load amount) for each card, in whole currency units. Must be a whole number ≥ the program minimum. | | `offerId` | `String!` | Yes | UUID v4 of the merchant offer the card is tied to. The offer must be active and its merchant must be shareable. | | `quantity` | `Int!` | Yes | Number of links to generate. One distinct hosted URL is created per unit. | | `shareMethod` | `ShareMethodType!` | Yes | How recipients are identified and links are delivered: `GENERATE_URL`, `EMAIL`, `PHONE_NUMBER`, `EXISTING_USER`, or `REGISTER_USER`. | | `userCashBalanceId` | `UUID` | Yes\* | The spend account used to fund the cards. *Marked optional in the schema but required in practice — omitting it fails validation.* | | `daysUntilExpiration` | `Int` | No | Days until the link expires. Minimum 1. Defaults to the program default (30 days) if omitted. **This date also becomes the card's lock/freeze date** — see [Expiration & freeze](#expiration--freeze). | | `recipientListEmail` | `[String]` | Conditional | Required and non-empty when `shareMethod = EMAIL`. Length must equal `quantity`. Must be empty otherwise. | | `recipientListPhone` | `[String]` | Conditional | Required and non-empty when `shareMethod = PHONE_NUMBER`. Length must equal `quantity`. Must be empty otherwise. | | `recipientUserIds` | `[UUID]` | Conditional | Known Fluz user IDs to bind as recipients when `shareMethod = EXISTING_USER`. Length must equal `quantity`. Mutually exclusive with `recipientRegistrations`. | | `recipientRegistrations` | `[ShareLinkRecipientRegistrationInput]` | Conditional | Inline pre-register payloads when `shareMethod = REGISTER_USER` — Fluz creates or reuses a placeholder user (no seat) per entry before generating links. Length must equal `quantity`. Mutually exclusive with `recipientUserIds`. | | `usePrepaymentBalance` | `Boolean` | No | Set whether to use your Prepayment Balance as an additional funding source. The default is false. More information about how this is used: [How funding works](/features/virtual-cards#how-funding-works). | | `useRewardsBalance` | `Boolean` | No | Set whether to use your Fluz Rewards Balance as an additional funding source. The default is false. More information about how this is used: [How funding works](/features/virtual-cards#how-funding-works). | **Funding source.** `userCashBalanceId` (a spend account belonging to your, the sender's, account) is the primary and required funding source. Optionally set `usePrepaymentBalance` and/or `useRewardsBalance` to `true` to let Fluz fall back to your prepayment or rewards balance if the spend account doesn't cover the full amount at claim time. Bank accounts and bank cards are not supported as funding sources. ### Recipient identification & delivery (`shareMethod`) | Value | Behavior | Recipient field | Card created | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------- | | `GENERATE_URL` | Fluz returns the hosted URLs in the response. You distribute them yourself. | Both lists must be empty/omitted. | At claim | | `EMAIL` | Fluz emails a link to each recipient. | `recipientListEmail` required; length must equal `quantity`. | At claim | | `PHONE_NUMBER` | Fluz texts a link to each recipient. | `recipientListPhone` required; length must equal `quantity`. | At claim | | `EXISTING_USER` | Link is bound to a known Fluz user from the start; only that user can claim it. Delivered via the contact channel(s) already on file for that user. | `recipientUserIds` required; length must equal `quantity`. | **Immediately**, at generation time | | `REGISTER_USER` | Fluz creates or reuses a placeholder user per entry, then binds the link to that user, same as `EXISTING_USER`. | `recipientRegistrations` required; length must equal `quantity`. | **Immediately**, at generation time | `EXISTING_USER` and `REGISTER_USER` both create the virtual card as part of the `generateVCShareLinks` call, rather than deferring card creation to claim time. See [Register & Send](/features/open-loop-cards/register-and-send) for the full `EXISTING_USER` flow, including how to register a recipient first with `registerUser`. ### Validation rules * `cardLimit` must be a whole number and at least the program minimum. * `offerId` must be a valid UUID v4 for an **active** offer whose **merchant is shareable**. * `quantity` must be a whole number. * The recipient field matching `shareMethod` (`recipientListEmail`, `recipientListPhone`, `recipientUserIds`, or `recipientRegistrations`) must have length **equal to** `quantity`. Mismatches return a clear error and create **no** records. * `recipientUserIds` and `recipientRegistrations` are mutually exclusive with each other and with the delivery-list fields. * Each ID in `recipientUserIds` must be a valid, existing Fluz user. * `userCashBalanceId` is required and must be a valid UUID v4 owned by the sender's account. `usePrepaymentBalance` and `useRewardsBalance` are optional fallback sources and may both be enabled alongside it. * Invalid card types or malformed inputs return clear errors and create no records. ### Examples ```json Generate URLs theme={null} { "input": { "cardLimit": 25, "offerId": "11111111-2222-3333-4444-555555555555", "daysUntilExpiration": 30, "quantity": 3, "shareMethod": "GENERATE_URL", "userCashBalanceId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" } } ``` ```json Email theme={null} { "input": { "cardLimit": 25, "offerId": "11111111-2222-3333-4444-555555555555", "daysUntilExpiration": 30, "quantity": 2, "shareMethod": "EMAIL", "recipientListEmail": ["recipient1@example.com", "recipient2@example.com"], "userCashBalanceId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" } } ``` ```json SMS theme={null} { "input": { "cardLimit": 25, "offerId": "11111111-2222-3333-4444-555555555555", "daysUntilExpiration": 30, "quantity": 2, "shareMethod": "PHONE_NUMBER", "recipientListPhone": ["+12125550101", "+12125550102"], "userCashBalanceId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" } } ``` ```json Existing user theme={null} { "input": { "cardLimit": 25, "offerId": "11111111-2222-3333-4444-555555555555", "daysUntilExpiration": 30, "quantity": 1, "shareMethod": "EXISTING_USER", "recipientUserIds": ["f1320ac4-52dc-4c67-9e80-24e506b18450"], "userCashBalanceId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" } } ``` For `EXISTING_USER`, register the recipient first (or use an existing user's ID directly) — see [Register & Send](/features/open-loop-cards/register-and-send) for the full walkthrough, including the `registerUser` call and response handling. ### Response ```json theme={null} { "data": { "generateVCShareLinks": { "shareLinks": [ "https://fluz.app/virtual-prepaid-card/3f8a...c1", "https://fluz.app/virtual-prepaid-card/9b2d...77", "https://fluz.app/virtual-prepaid-card/0c41...e3" ] } } } ``` `shareLinks` is an array of hosted URLs, one per `quantity`, each of the form `https://fluz.app/virtual-prepaid-card/{share_request_id}`. The response returns only the URLs. To retrieve the **batch ID** and **display IDs** for the links you just created (needed for listing and deactivation), use `getVCShareLinks` filtered by status. ## getVCShareLinks Lists previously generated share links so you can inspect status, recipients, expiration, and the issued card. ```graphql theme={null} query GetVCShareLinks($input: GetVCShareLinksInput!) { getVCShareLinks(input: $input) { senderAppId shareRequestBatchId shareRequestDisplayId shareObjectStatus recipientPhone recipientEmail linkExpirationDate virtualCardId linkUrl shareRequestDetails { cardLimit offerId daysUntilExpiration quantity shareMethod recipientListEmail recipientListPhone userCashBalanceId } } } ``` ### Input fields | Field | Type | Description | | ------------------------ | --------------------- | --------------------------------------------------------- | | `shareObjectStatuses` | `[ShareObjectStatus]` | Filter by status: `PENDING`, `ISSUED`, `USED`, `EXPIRED`. | | `shareRequestBatchIds` | `[String]` | Return only links in these batches. | | `shareRequestDisplayIds` | `[String]` | Return only the links with these display IDs. | **Recommended flow.** On the first call, filter by `shareObjectStatuses` only. The response gives you `shareRequestBatchId` and `shareRequestDisplayId` values; use those to filter precisely on subsequent calls (and to deactivate). ### Response fields (`GeneratedShareLink`) | Field | Type | Description | | ----------------------- | --------------------- | ---------------------------------------------------------------------------- | | `senderAppId` | `String` | The app/developer application that generated the link. | | `shareRequestBatchId` | `String` | Batch identifier shared by all links generated in one call. | | `shareRequestDisplayId` | `String` | Human-friendly per-link identifier. | | `shareObjectStatus` | `ShareObjectStatus` | `PENDING`, `ISSUED`, `USED`, or `EXPIRED`. | | `recipientEmail` | `String` | Recipient email, if delivered by email. | | `recipientPhone` | `String` | Recipient phone, if delivered by SMS. | | `linkExpirationDate` | `DateTime` | When the link expires / card freezes. | | `virtualCardId` | `String` | The issued virtual card ID, once claimed. | | `linkUrl` | `String` | The hosted URL for the link. | | `shareRequestDetails` | `ShareRequestDetails` | The original configuration (card limit, offer, quantity, delivery, funding). | ### Examples ```json By status theme={null} { "input": { "shareObjectStatuses": ["PENDING", "ISSUED"] } } ``` ```json By batch theme={null} { "input": { "shareRequestBatchIds": ["ABC123", "XYZ789"] } } ``` ```json By display ID theme={null} { "input": { "shareRequestDisplayIds": ["SR-000001", "SR-000002"] } } ``` ## deactivateVCShareLinks Deactivates (expires) links you generated — for example, if a batch was sent in error or you need to revoke unclaimed links. Deactivating a link sets it to `EXPIRED`; an unclaimed link can no longer be claimed. ```graphql theme={null} mutation DeactivateVCShareLinks($input: DeactivateVCShareLinksInput!) { deactivateVCShareLinks(input: $input) } ``` ### Input fields | Field | Type | Description | | ------------------------ | ---------- | ------------------------------------------------- | | `shareRequestBatchIds` | `[String]` | Deactivate every link in these batches. | | `shareRequestDisplayIds` | `[String]` | Deactivate only the links with these display IDs. | Get the batch IDs from `getVCShareLinks`. ```json theme={null} { "input": { "shareRequestBatchIds": ["ABC123"] } } ``` Returns a human-readable confirmation string, e.g. `"3 share requests successfully deactivated!"`. If a recipient has already **claimed** a link (status `ISSUED`/`USED`), deactivating the link does not claw back the issued card. To stop spend on an already-issued card, use the relevant card lifecycle/freeze controls. ## Expiration & freeze The link's expiration date does double duty: * **Link expiration** — after this date, an **unclaimed** link can no longer be claimed. * **Card freeze / lock date** — for an **issued** card, this is the lock date (end of that day). After it, the card is frozen and cannot be spent. * **Card expiry** is aligned to the end of the month of the freeze date (e.g., a freeze date of 6/15/2026 yields a card expiry of 6/30/2026). Set the window with `daysUntilExpiration` at generation time. If omitted, the program default (30 days) is used. This date is shown to the recipient (typically as a "Valid until" date) — see [Recipient Experience](/features/open-loop-cards/open-loop-cards-recipient-experience). ## Status & error reference ### Share object statuses | Status | Meaning | | --------- | ------------------------------------------------------ | | `PENDING` | Link generated, not yet claimed. | | `ISSUED` | Recipient claimed the link; a virtual card was issued. | | `USED` | The issued card has been used. | | `EXPIRED` | Link expired or was deactivated; no longer claimable. | For the states a recipient sees when a link is expired, revoked, or already claimed, see [Recipient-facing link errors](/features/open-loop-cards/open-loop-cards-recipient-experience#recipient-facing-link-errors). ### Common API errors | Cause | Result | | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | | Missing/invalid Bearer token or missing `CREATE_SHARE_LINK` scope | Request rejected (unauthorized). | | Recipient field (`recipientListEmail`, `recipientListPhone`, `recipientUserIds`, or `recipientRegistrations`) length ≠ `quantity` | Clear validation error; no records created. | | `recipientUserIds` references a non-existent Fluz user | Validation error; no records created. | | Inactive offer, non-shareable merchant, or invalid `offerId` | Validation error; no records created. | | Missing/invalid `userCashBalanceId` | Validation error; no records created. | ## Notes & limitations * **Returned URL is the hosted destination, not a short link.** Internally, links are also wrapped by a short-link provider, but the API returns the canonical hosted URL (`/virtual-prepaid-card/{share_request_id}`). Distribute the URL exactly as returned. * `userCashBalanceId` is effectively required even though the schema marks it optional. * **Hidden/internal fields are not part of this API.** Object type and card type are fixed (`VIRTUAL_CARD` / `SINGLE_LOAD`). Bank account and bank card funding are not yet enabled; do not send them. `usePrepaymentBalance` and `useRewardsBalance` are the only supported additional funding sources today. * **Gift-card hosted links are not supported.** This API is for virtual cards only. ## Next steps What your recipients see when they open a hosted link, and the rules that govern their card. Use `EXISTING_USER` to register a recipient and create their card up front, instead of at claim. Issue many cards at once for programmatic distribution. # Primary & Backup Funding Source: https://docs.fluz.app/features/primary-and-backup-funding Every Fluz account funds transactions from a **default funding source** built on two roles — a **primary** bank account and a **backup** card. The primary is charged first; the backup steps in when the primary can't be. This page covers how funding works and how to read and change both roles, over the API or in the app. | Role | Shown in app as | Must be a | Purpose | | ----------- | ----------------- | ------------------------------------ | ---------------------------------------------------------------- | | **Primary** | Preferred account | Bank account (ACH) | The default funding source, charged first for every transaction. | | **Backup** | Backup card | Bank card (debit / credit / prepaid) | Charged if the primary funding source can't be. | Each account has exactly **one primary and one backup** at a time. Selecting a new funding source for a role replaces the previous one. *** ## How funding works When a user makes a purchase, funds a virtual card, or deposits to their Fluz balance, Fluz charges the **primary** funding source first — by default, the preferred bank account over ACH. Because ACH can take several days to settle (and can fail), the **backup card** acts as a safety net: 1. When a user pays via ACH, a temporary **hold** may be placed on the backup card for the transaction amount. 2. If the ACH payment **clears**, the hold is released within **1–7 business days**. 3. If the ACH payment **does not clear**, the **backup card is charged** instead. ```mermaid theme={null} flowchart TD A([Transaction initiated]) --> B{Charge primary
bank account via ACH} B -->|ACH accepted| C[Temporary hold placed
on backup card] C --> D{ACH clears?} D -->|Yes| E([Hold released in
1–7 business days]) D -->|No| F([Backup card charged]) B -->|Primary can't be charged| F ``` > ❗️ A backup card must be on file before a transaction can complete > > Fluz requires a valid bank card as the backup so a transaction can still settle if the primary funding source fails. Without one, transactions that depend on ACH **cannot be started**. *** ## Configuring primary & backup Primary and backup selection can be managed **via the API** or **in the Fluz app / web portal**. Selecting a new funding source for a role replaces the previous one for that account. ### Via the API > 📘 Authentication & scopes > > These operations require a **user access token**. Reads (`getDefaultFundingSource`) use the `LIST_PAYMENT` scope; changes (`setPrimaryFundingSource`, `setBackupFundingSource`) use `MANAGE_PAYMENT`. #### Read the current default funding source Returns both roles for the account. `primary` is `null` when no primary bank account is set; `backup` is `null` when no backup card is set. ```graphql Query theme={null} query { getDefaultFundingSource { primary { bankAccountId accountName status lastFour } backup { bankCardId lastFourDigits cardType cardStatus } } } ``` ```json Response theme={null} { "data": { "getDefaultFundingSource": { "primary": { "bankAccountId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "accountName": "Chase Total Checking", "status": "ENABLED", "lastFour": "6789" }, "backup": { "bankCardId": "16fd2706-8baf-433b-82eb-8c7fada847da", "lastFourDigits": "4242", "cardType": "CREDIT", "cardStatus": "ACTIVE" } } } } ``` **Response fields** | Field | Type | Description | | ----------------------- | -------------- | --------------------------------------------------- | | `primary` | object \| null | The primary bank account, or `null` if none is set. | | `primary.bankAccountId` | ID | UUID of the linked bank account. | | `primary.accountName` | string | Display name of the bank account. | | `primary.status` | enum | Bank account status (e.g. `ENABLED`). | | `primary.lastFour` | string | Last four digits of the account number. | | `backup` | object \| null | The backup bank card, or `null` if none is set. | | `backup.bankCardId` | ID | UUID of the bank card. | | `backup.lastFourDigits` | string | Last four digits of the card. | | `backup.cardType` | enum | Card type — `DEBIT`, `CREDIT`, or `PREPAID`. | | `backup.cardStatus` | enum | Card status (e.g. `ACTIVE`). | > 👍 One call, two reads > > The same `DefaultFundingSource` object is also returned inside `getWallet { defaultFundingSource { … } }` — so you can read the current selection in the same call you use to list wallet contents. #### Set the primary funding source (a bank account) ```graphql Mutation theme={null} mutation { setPrimaryFundingSource(input: { bankAccountId: "d290f1ee-6c54-4b01-90e6-d701748f0851" }) { primary { bankAccountId accountName } backup { bankCardId lastFourDigits } } } ``` ```json Response theme={null} { "data": { "setPrimaryFundingSource": { "primary": { "bankAccountId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "accountName": "Chase Total Checking" }, "backup": { "bankCardId": "16fd2706-8baf-433b-82eb-8c7fada847da", "lastFourDigits": "4242" } } } } ``` | Argument | Type | Required | Description | | --------------------- | --------- | -------- | ----------------------------------------------------------------------------------------------- | | `input.bankAccountId` | ID (UUID) | Yes | A bank account linked to the account and **enabled**. Becomes the funding source charged first. | #### Set the backup funding source (a bank card) ```graphql Mutation theme={null} mutation { setBackupFundingSource(input: { bankCardId: "16fd2706-8baf-433b-82eb-8c7fada847da" }) { primary { bankAccountId accountName } backup { bankCardId lastFourDigits } } } ``` ```json Response theme={null} { "data": { "setBackupFundingSource": { "primary": { "bankAccountId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "accountName": "Chase Total Checking" }, "backup": { "bankCardId": "16fd2706-8baf-433b-82eb-8c7fada847da", "lastFourDigits": "4242" } } } } ``` | Argument | Type | Required | Description | | ------------------ | --------- | -------- | ----------------------------------------------------------------------------------- | | `input.bankCardId` | ID (UUID) | Yes | A bank card on the account that is **active**. Becomes the fallback funding source. | > 📘 Both mutations return the account's full `DefaultFundingSource` > > Each mutation returns **primary + backup** after the change — so a single call updates one role and confirms the resulting state. #### Validation rules * The `bankAccountId` must be a bank account **linked to the account and enabled**; the `bankCardId` must be a bank card **on the account and active**. * The primary must be a **bank account** and the backup a **bank card** — a mismatch is rejected. * Setting a role **replaces** the account's previous selection (one primary, one backup at a time). #### Errors | Situation | `errorName` | `code` | `statusCode` | `message` | | ------------------------------------------------ | -------------------- | ---------- | ------------ | ------------------------------------------------------------------------------------- | | Bank account not found / not owned / not enabled | `InvalidBankAccount` | `BA-0001` | 400 | Please verify that your account is active and up-to-date and try again. | | Bank card not found / not owned / not active | `InvalidBankCard` | `BC-0001` | 400 | Looks like this bank card is not a valid card, please select a new card and try again | | Malformed input (e.g. not a UUID) | `InvalidArguments` | `ARG-0001` | 422 | Invalid arguments received *(not user-friendly)* | ```json Example error theme={null} { "errors": [ { "message": "Please verify that your account is active and up-to-date and try again.", "extensions": { "errorName": "InvalidBankAccount", "code": "BA-0001", "statusCode": 400, "userFriendly": true } } ], "data": { "setPrimaryFundingSource": null } } ``` The structured error lives under `extensions`, keyed off `code` / `errorName`. `statusCode` is a *semantic* status (e.g. `400`, `422`) carried inside `extensions` — the GraphQL transport itself returns **HTTP 200 even for errors**, with the failure in the `errors` array and `data.` set to `null`. **Detect failures by checking for an `errors` array, not the HTTP status.** Some errors also carry optional `codeNumber` and `notificationDetails` keys, and `userFriendly` indicates whether `message` is safe to surface directly to end users. ### In the app / web **On the web** 1. Open **Accounts and Cards** from the account menu (or go to `/accounts-and-cards`). 2. In the **Default funding source** section at the top of the page: * Select **Manage preferred account** to choose the bank account used as the default funding source. * Select **Manage backup card** to choose the bank card charged if the preferred account can't be. 3. Your selection is saved immediately. **On mobile** 1. Open the menu and tap **Accounts and Cards**. 2. Under **Default funding source**, tap **Manage preferred account** or **Manage backup card**. 3. Choose from your linked funding sources. Your selection is saved immediately. If you don't yet have an eligible funding source, the app prompts you to add one first — a bank account for the preferred account, or a bank card for the backup. *** ## What can be done via API vs. app / web | Task | API | App / Web | | -------------------------------------------- | :-: | :-------: | | Add, update, or delete a bank card | ✓ | ✓ | | Link or remove a bank account (Plaid) | ✓ | ✓ | | Set which funding source is primary / backup | ✓ | ✓ | See [Bank Cards](doc:bank-cards) and [Bank Accounts](doc:bank-accounts) for the underlying funding-source operations available through the API. *** ## Eligibility & notes * The primary payment method must be a **linked bank account**; the backup must be a **bank card**. * Business accounts must complete **KYB verification** before funding sources — and therefore the default funding source — can be set. * Some funding sources may be restricted for specific merchants or by account limits and won't be selectable as a default. > 📘 Want to learn more? [Speak with our experts](doc:contact) for more info or to request a demo. # Query Authorized User Source: https://docs.fluz.app/features/query-authorized-user List authorized users on the caller's account. Results are always scoped to the caller's account — Bearer tokens use the token's account; Basic (API key) callers use the application's configured operator account. Role assignments for `OWNER` are excluded. Both filters are optional and narrow the results further by `email` and/or `phone`. 🔒 Restricted Access This query requires the `VIEW_SUBUSERS` scope. It supports both Bearer (user access token) and Basic (``) authentication. ```graphql theme={null} query AuthorizedUsers( $email: String $phone: String ) { authorizedUsers( email: $email phone: $phone ) { authUserId roles status email phone firstName lastName } } ```
### Parameters | Parameter | Type | Required | Description | | :-------- | :----- | :------- | :-------------------------------------------------- | | email | String | No | Filter by the email address of the authorized user. | | phone | String | No | Filter by the phone number of the authorized user. |
### Response #### Success Response ```json theme={null} { "data": { "authorizedUsers": [ { "authUserId": "8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d", "roles": ["MANAGER", "VIEWER"], "status": "ACTIVE", "email": "teammate@example.com", "phone": "+15555555555", "firstName": "Ada", "lastName": "Lovelace" } ] } } ```
### Response Fields | Field | Type | Description | | :----------- | :---------------- | :------------------------------------------------------------------------------------------ | | `authUserId` | UUID | The authorized user ID (UAC role assignment ID). Pass this value to `removeAuthorizedUser`. | | `roles` | \[UACRoleType] | Roles assigned to the user on the account. | | `status` | UACRoleStatusType | Status of the role assignment: `PENDING`, `ACTIVE`, `INACTIVE`, or `DECLINED`. | | `email` | String | Email address of the authorized user. | | `phone` | String | Phone number of the authorized user. | | `firstName` | String | First name of the authorized user. | | `lastName` | String | Last name of the authorized user. |
### Example Request ```curl theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "query AuthorizedUsers($email: String, $phone: String) { authorizedUsers(email: $email, phone: $phone) { authUserId roles status email phone firstName lastName } }", "variables": { "email": "teammate@example.com" } }' ``` ```typescript theme={null} const response = await fetch('https://transactional-graph.staging.fluzapp.com/api/v1/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, body: JSON.stringify({ query: ` query AuthorizedUsers( $email: String $phone: String ) { authorizedUsers( email: $email phone: $phone ) { authUserId roles status email phone firstName lastName } } `, variables: { email: "teammate@example.com" } }) }); const data = await response.json(); console.log('Authorized users:', data.data.authorizedUsers); ``` ### Error Codes | Code | Message | Description | | :---------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AUTH-0008` | Invalid user access | The caller could not be resolved from the access token or API key, or the Basic-auth application has no operator account configured. Verify your authentication credentials. | | `AUTH-0031` | The requested scopes must be granted by the user first. | The token is missing the `VIEW_SUBUSERS` scope required to list authorized users. |
# Wallet Transfer Recipient Lookup Source: https://docs.fluz.app/features/recipient-lookup Used to retrieve the `account_id` of the account for transfering funds.
### Overview Two queries for looking up recipients for transfer operations: * `lookupUser` - Find a user by phone or email * `lookupBusiness` - Find businesses by company name *** ### lookupUser Look up an individual user recipient. #### Query (UserLookupInput) ```graphql theme={null} lookupUser(input: UserLookupInput!): UserLookupResult! ``` **Requires**: `QUERY_RECIPIENT` scope. #### Input | Field | Type | Required | Description | | :---------- | :----- | :------- | :--------------------------------------------------------------------- | | phoneNumber | String | No\* | The phone number of the recipient in E.164 format (e.g., +14155551234) | | email | String | No\* | The email address of the recipient | *Provide exactly one field* #### Response (UserLookupResult) | Field | Type | Description | | :---------- | :------ | :-------------------------- | | recipientId | UUID! | Recipient's account ID | | name | String! | Display name ("First Last") | #### Examples **By phone:** ```graphql theme={null} query { lookupUser(input: { phoneNumber: "+14155551234" }) { recipientId name } } ``` **By email:** ```graphql theme={null} query { lookupUser(input: { email: "jane@example.com" }) { recipientId name } } ``` **Success:** ```json theme={null} { "data": { "lookupUser": { "recipientId": "a2c02ec6-357b-41cf-9229-4da34e727019", "name": "John Doe" } } } ``` **Not found:** ```json theme={null} { "errors": [{ "message": "No recipient found with the provided information.", "extensions": { "code": "RC-0001" } }] } ``` #### Error Codes | Code | Description | | :------ | :---------------------- | | RC-0001 | Recipient not found | | RC-0002 | Multiple matches (rare) | | AR-0001 | Invalid/missing input | | GE-9999 | Server error | *** ### lookupBusiness Look up business recipients by company name. #### Query ```graphql theme={null} lookupBusiness(input: BusinessLookupInput!): [BusinessLookupResult!]! ``` **Requires**: `QUERY_RECIPIENT` scope. #### Input | Field | Type | Required | Description | | :---------- | :----- | :------- | :------------------------------------------- | | companyName | String | Yes | Company name (case-insensitive, exact match) | **Note:** Searches both registered business name and DBA name. Must match exactly (case-insensitive). #### Response | Field | Type | Description | | :---------- | :------ | :---------------------------------------- | | recipientId | UUID! | Recipient's account ID | | companyName | String! | Formatted name: "Company Name (DBA Name)" | | state | String | State from business legal address | #### Examples ```graphql theme={null} query { lookupBusiness(input: { companyName: "acme corporation" }) { recipientId companyName state } } ``` **Single match:** ```json theme={null} { "data": { "lookupBusiness": [ { "recipientId": "c4e24ge8-579d-63eh-b451-6fc56g949241", "companyName": "Acme Corporation", "state": "CA" } ] } } ``` **Multiple matches (same name, different DBAs):** ```json theme={null} { "data": { "lookupBusiness": [ { "recipientId": "c4e24ge8-579d-63eh-b451-6fc56g949241", "companyName": "Acme Corporation (Acme Retail)", "state": "CA" }, { "recipientId": "d5f35hf9-680e-74fi-c562-7gd67h050352", "companyName": "Acme Corporation (Acme Wholesale)", "state": "NY" } ] } } ``` **Not found:** ```json theme={null} { "errors": [{ "message": "No recipient found with the provided information.", "extensions": { "code": "RC-0001" } }] } ``` #### Error Codes | Code | Description | | :------ | :-------------------- | | RC-0001 | Recipient not found | | AR-0001 | Invalid/missing input | | GE-9999 | Server error | ***
### Important Notes #### lookupUser * Phone numbers must be in E.164 format (e.g., +14155551234) * 10-digit US numbers are auto-normalized * Email is case-insensitive * Throws error if not found ### lookupBusiness * Case-insensitive exact match - "acme corporation" matches "ACME CORPORATION" or "Acme Corporation" * Searches both business name and DBA name * Returns array (may contain multiple businesses with the same name) * Throws error if no matches found * `companyName` is formatted as "Business Name (DBA Name)" when DBA differs from business name # Redeem Fluz Gift Card Source: https://docs.fluz.app/features/redeem-fluz-gift-card The `redeemFluzGiftCard` mutation allows you to redeem a Fluz Gift Card to the authenticated user’s account using the gift card code. Successful redemption credits the user’s **gift card balance** (`giftCardCashBalance`). This mutation requires the `MAKE_DEPOSIT` scope. *** ### Arguments * **`input`** (`RedeemFluzGiftCardInput!`): The input object containing the idempotency key and gift card code. *** ### RedeemFluzGiftCardInput Fields | Field | Type | Description | Required | | ---------------- | --------- | ------------------------------------------------------------------------------ | -------- | | `idempotencyKey` | `UUID!` | Unique client-generated key to ensure the request is processed only once. | Yes | | `code` | `String!` | The Fluz Gift Card code. Leading/trailing whitespace is automatically trimmed. | Yes | *** ### Response Returns a `DepositCashBalanceResponse`, including: * **`cashBalanceDeposits`** — Deposit records created for this redemption * **`balances`** — Updated user balances after redemption > The redeemed value is credited to `balances.giftCardCashBalance` (not `cashBalance`). *** ### Sample Mutation ```graphql theme={null} mutation RedeemGC($input: RedeemFluzGiftCardInput!) { redeemFluzGiftCard(input: $input) { cashBalanceDeposits { cashBalanceDepositId depositDisplayId depositAmount depositFee status cashBalanceDepositType transactionDate clearedDate cashBalanceSettlements { cashBalanceSettlementId availabilityType status } } balances { cashBalance { availableBalance totalBalance pendingBalance lifetimeBalance } giftCardCashBalance { availableBalance totalBalance lifetimeBalance } rewardsBalance { availableBalance totalBalance lifetimeBalance } } } } ``` *** ### Variables ```json theme={null} { "input": { "idempotencyKey": "ee940ccc-2d24-4e1f-8c81-1a3a8b59fa01", "code": "FLUZ-GC-CODE-EXAMPLE" } } ``` *** ### cURL Example ```bash theme={null} curl -X POST \ https://transactional-graph-service.fluzapp.com/api/v1/graphql \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \ -d '{ "query": "mutation RedeemGC($input: RedeemFluzGiftCardInput!) { redeemFluzGiftCard(input: $input) { cashBalanceDeposits { cashBalanceDepositId depositDisplayId depositAmount depositFee status cashBalanceDepositType transactionDate clearedDate cashBalanceSettlements { cashBalanceSettlementId availabilityType status } } balances { cashBalance { availableBalance totalBalance pendingBalance lifetimeBalance } giftCardCashBalance { availableBalance totalBalance lifetimeBalance } rewardsBalance { availableBalance totalBalance lifetimeBalance } } } }", "variables": { "input": { "idempotencyKey": "ee940ccc-2d24-4e1f-8c81-1a3a8b59fa01", "code": "FLUZ-GC-CODE-EXAMPLE" } } }' ``` *** ### Behavior * **Balance destination** — Funds are credited to `giftCardCashBalance`. * **Idempotency** — Duplicate requests with the same `idempotencyKey` are rejected while in-flight. * **Whitespace handling** — Leading/trailing spaces in `code` are trimmed automatically. * **Fees** — Any activation fee is reflected in `depositFee`. * **Settlement** — Redemptions are **instant** (`INSTANT`, `AVAILABLE`). *** ### Errors | Code | Description | | ---------- | ------------------------------------------------------------ | | `ARG-XXXX` | Missing or invalid `idempotencyKey` or `code`. | | `GC-0003` | Invalid, inactive, or already redeemed gift card. | | `G-0409` | Duplicate request in progress for the same `idempotencyKey`. | | `G-0004` | General failure (e.g., feature access, internal error). | # Relinking Bank Accounts Source: https://docs.fluz.app/features/relink-accounts A previously linked Plaid connection can drop — for example, when the user changes their bank credentials. While it's disconnected, Fluz can't fetch updated balances, so it must be repaired with a Plaid **update-mode** Link flow before balances and spend power will refresh again. ## Auth Call `/api/v1/graphql` with a Fluz user Bearer access token that includes `MANAGE_PAYMENT`. Basic auth is not allowed on these fields. ```http theme={null} Authorization: Bearer ``` ## Detecting that a relink is needed There are two reliable signals: 1. `refreshPlaidBankConnections `**returns** `verifyMembers`**.** Each entry identifies a connection that needs repair; use its `platformItemId` to start the relink flow below. (See *Managing Bank Account Spend Power* for that mutation.) 2. `createPlaidLinkToken `**fails** with `No active Plaid connection found for the provided platformItemId.` — the stored connection is no longer relinkable; guide the user through a **new** bank link instead (see *Linking a Plaid Bank Account*). ## Relink flow You'll need the stored `platformItemId` for the disconnected connection. ### 1. Create a Link token with the stored `platformItemId` ```graphql theme={null} mutation CreatePlaidLinkToken($input: CreatePlaidLinkTokenInput!) { createPlaidLinkToken(input: $input) { linkToken expiration requestId mode } } ``` ```json theme={null} { "input": { "platformItemId": "plaid-item-id" } } ``` TGS uses `platformItemId` to retrieve the Plaid access token from identity-service, then asks identity-service to create a Plaid update-mode Link token. The access token is never returned to the caller. For **native** relink, also pass `deviceOs` as `IOS` or `ANDROID`. ### 2. Open Plaid Link Initialize Plaid Link with the returned `linkToken`. ### 3. Complete the relink In Plaid Link's `onSuccess`, complete the flow with **both** `publicToken` and the same `platformItemId`. ```graphql theme={null} mutation CompletePlaidLink($input: CompletePlaidLinkInput!) { completePlaidLink(input: $input) { requiresAddress bankAccountId bankInstitutionAuthId newlyLinkedBankInstitutionAuthId bankInstitutionName platformItemId bankAccounts { bankInstitutionAuthId bankAccountId bankName lastFour type subtype } } } ``` ```json theme={null} { "input": { "publicToken": "public-sandbox-...", "platformItemId": "plaid-item-id" } } ``` ### 4. Update the stored `platformItemId` Update your stored `platformItemId` from the response if it changed. If it isn't returned, call `getPlaidBankAccounts` for the user and store the persisted `platformItemId` from that response. ## Using the Web SDK for relink The shared `startPlaidLink` helper in *Linking a Plaid Bank Account* handles both cases. Call `startPlaidLink(existingPlatformItemId)` to repair a disconnected connection; call `startPlaidLink()` with no argument for a brand-new link. ## Error handling If `createPlaidLinkToken` fails with `No active Plaid connection found for the provided platformItemId.`, treat the stored connection as no longer relinkable and guide the user through a new bank link. If a user starts a new link for the same institution instead of choosing relink, complete it as a normal new link — identity-service owns bank-account dedupe and repair. Store the returned `platformItemId` after completion.
# Remove Authorized User Source: https://docs.fluz.app/features/remove-authorized-user Remove an authorized user from the caller's account by deactivating their role assignment. This mutation does not delete the user — it sets the role assignment on the caller's account to `INACTIVE`, revoking their access. The account owner (`OWNER` role) cannot be removed through this endpoint. The target account is always resolved from the caller's credentials — Bearer tokens use the token's account; Basic (API key) callers use the application's configured operator account. The `authUserId` must refer to a role assignment on that account; otherwise the request is rejected. 🔒 Restricted Access This mutation requires the `MANAGE_SUBUSERS` scope. It supports both Bearer (user access token) and Basic (``) authentication. ```graphql theme={null} mutation RemoveAuthorizedUser( $authUserId: UUID! ) { removeAuthorizedUser( authUserId: $authUserId ) { success authUserId status error { code message } } } ```
### Parameters | Parameter | Type | Required | Description | | :--------- | :--- | :------- | :------------------------------------------------------------------------------------------------------------------------ | | authUserId | UUID | Yes | The authorized user ID (UAC role assignment ID) to deactivate. Obtain this from `authorizedUsers` or `addAuthorizedUser`. |
### Response #### Success Response ```json theme={null} { "data": { "removeAuthorizedUser": { "success": true, "authUserId": "8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d", "status": "INACTIVE", "error": null } } } ```
### Response Fields | Field | Type | Description | | :----------- | :------------------ | :---------------------------------------------------------------------- | | `success` | Boolean | `true` if the role assignment was successfully deactivated. | | `authUserId` | UUID | The authorized user ID (UAC role assignment ID) that was updated. | | `status` | UACRoleStatusType | Updated status of the role assignment. Will be `INACTIVE` on success. | | `error` | AuthorizedUserError | If `success` is false, an Error object containing `code` and `message`. |
This mutation returns errors in the response data, not as GraphQL errors. Always check the `success` field and handle the `error` object when `success` is false. ### Example Request ```curl theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation RemoveAuthorizedUser($authUserId: UUID!) { removeAuthorizedUser(authUserId: $authUserId) { success authUserId status error { code message } } }", "variables": { "authUserId": "8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d" } }' ``` ```typescript theme={null} const response = await fetch('https://transactional-graph.staging.fluzapp.com/api/v1/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, body: JSON.stringify({ query: ` mutation RemoveAuthorizedUser( $authUserId: UUID! ) { removeAuthorizedUser( authUserId: $authUserId ) { success authUserId status error { code message } } } `, variables: { authUserId: "8b2c1e0a-7d4f-4a9b-9c2d-1f3e4a5b6c7d" } }) }); const data = await response.json(); if (data.data.removeAuthorizedUser.success) { console.log('Authorized user removed:', data.data.removeAuthorizedUser); } else { console.error('Remove authorized user failed:', data.data.removeAuthorizedUser.error); } ``` ### Error Codes | Code | Message | Description | | :---------- | :---------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ARG-0002` | Missing required arguments | `authUserId` was not provided. | | `AUTH-0008` | Invalid user access | The caller could not be resolved from the access token or API key, or the Basic-auth application has no operator account configured. Verify your authentication credentials. | | `AUTH-0031` | The requested scopes must be granted by the user first. | The token is missing the `MANAGE_SUBUSERS` scope required to manage authorized users. | | `AUTH-0034` | No role assignment found for the provided authorized user on the specified account. | The `authUserId` does not exist on the caller's account, or the assignment is already `INACTIVE`. | | `AUTH-0036` | The account owner cannot be removed. | The referenced role assignment holds the `OWNER` role and cannot be removed through this endpoint. | | `AUTH-0037` | Unable to manage authorized user. Please try again or contact support. | A general failure occurred while deactivating the role assignment. Please retry or contact support. |
# Request Account Transfer Approval Source: https://docs.fluz.app/features/request-account-transfer-approval `requestAccountTransfer` Submit a manager approval request for an account-to-account transfer. When approved, Fluz executes the transfer using the same semantics as `createTransfer`. Use this when a user or application needs manager sign-off before moving funds between Fluz accounts. For transfers between spend accounts on the same account, use `requestInternalTransfer` instead. ## Scopes | Action | Scope | | ------------------ | -------------------------- | | Create request | `REQUEST_ACCOUNT_TRANSFER` | | List requests | `LIST_APPROVALS` | | Approve or decline | `MANAGE_APPROVALS` | ## Webhook Identifiers | Field | Value | | -------------- | -------- | | `approvalType` | `PAYOUT` | | `approvalCode` | `500402` | Webhook events: `APPROVAL_CREATE`, `APPROVAL_APPROVE`, `APPROVAL_DECLINE`, and `APPROVAL_HANDLER_ERROR` on execution failure. > Internal transfers and account transfers share the same `approvalType` and `approvalCode`. Use the original request mutation or inspect the approval payload in your integration to distinguish them. ## Create a Request Input mirrors [Transfer to Another Fluz Account](/features/transfer-to-another-fluz-wallet). The sender is determined from authentication; destination and funding sources follow the same rules as `createTransfer`. ### RequestAccountTransferInput | Field | Type | Required | Description | | --------------------- | ------------------- | -------- | ---------------------------------------------------------------------------------------------- | | `idempotencyKey` | UUID | Yes | Unique key to prevent duplicate transfers on approval | | `amount` | Float | Yes | Amount to transfer. Must be greater than zero | | `destination` | TransferDestination | Depends | Recipient account. Required for Basic Auth; optional for Bearer (defaults to your application) | | `bankCardId` | UUID | No | Fund from a linked bank card. Bearer token only | | `bankAccountId` | UUID | No | Fund via ACH. Bearer token only | | `paypalVaultId` | UUID | No | Fund from PayPal. Bearer token only | | `memo` | String | No | Transaction memo | | `transactionCategory` | String | No | Category label; created automatically on first use | | `attachmentId` | String | No | ID of a previously uploaded transaction attachment | ### TransferDestination | Field | Type | Description | | --------------------- | ------ | ------------------------------------------------------ | | `accountId` | UUID | Recipient Fluz account ID | | `externalReferenceId` | String | Recipient external reference ID from your system | | `userCashBalanceId` | UUID | Optional target spend account on the recipient account | Provide either `accountId` or `externalReferenceId`, not both. ### Authentication Notes | Method | Sender | Notes | | ------------ | ------------------ | ---------------------------------------------------------------------------- | | Bearer token | Authenticated user | Funding sources allowed; destination optional (defaults to your application) | | Basic Auth | Your application | Destination required; funding sources not allowed | ### Sample Mutation — User to Application ```graphql theme={null} mutation { requestAccountTransfer( input: { idempotencyKey: "550e8400-e29b-41d4-a716-446655440000" amount: 100.00 } ) { success messageId error { code message } } } ``` ### Sample Mutation — Application to User ```graphql theme={null} mutation { requestAccountTransfer( input: { idempotencyKey: "550e8400-e29b-41d4-a716-446655440001" amount: 50.00 destination: { accountId: "c3d4e5f6-a7b8-9012-cdef-345678901234" } } ) { success messageId error { code message } } } ``` ### Sample Response ```json theme={null} { "data": { "requestAccountTransfer": { "success": true, "messageId": "1234567890" } } } ``` The `idempotencyKey` is preserved and used when the transfer executes on approval. ## Approve a Request Call `approveApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { approveApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` On approval, Fluz executes the account transfer. ## Decline a Request Call `declineApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { declineApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` No transfer is executed when a request is declined. # Request Gift Card Purchase Approval Source: https://docs.fluz.app/features/request-gift-card-purchase-approval `requestGiftCardPurchase` Submit a manager approval request to purchase a gift card. When approved, Fluz completes the purchase using the submitted parameters. ## Scopes | Action | Scope | | ------------------ | ------------------- | | Create request | `REQUEST_GIFT_CARD` | | List requests | `LIST_APPROVALS` | | Approve or decline | `MANAGE_APPROVALS` | ## Webhook Identifiers | Field | Value | | -------------- | ----------- | | `approvalType` | `GIFT_CARD` | | `approvalCode` | `400007` | Webhook events: `APPROVAL_CREATE`, `APPROVAL_APPROVE`, `APPROVAL_DECLINE`, and `APPROVAL_HANDLER_ERROR` on execution failure. ## Create a Request Input fields mirror the direct `purchaseGiftCard` mutation. See [Purchase Gift Card](/purchase-gift-card) for offer and funding source details. ### RequestGiftCardPurchaseInput | Field | Type | Required | Description | | --------------------------- | --------------------- | -------- | ------------------------------------------- | | `offerId` | UUID | Yes | Gift card offer ID | | `purchaseAmount` | Float | Yes | Purchase amount | | `channel` | SourcePlatformChannel | Yes | Request origin channel (for example, `API`) | | `fluzpayAmount` | Float | No | Amount to fund from Fluz balance | | `bankCardId` | UUID | No | Bank card funding source | | `bankAccountId` | UUID | No | Bank account funding source | | `paypalVaultId` | UUID | No | PayPal funding source | | `userCashBalanceId` | UUID | No | Spend account to fund the purchase | | `seatId` | UUID | No | Seat associated with the purchase | | `exclusiveRateId` | UUID | No | Exclusive rate ID | | `merchantSlug` | String | No | Merchant slug | | `gcWarehouseMerchantId` | UUID | No | Gift card warehouse merchant ID | | `gcWarehouseUserRewardRate` | Float | No | Warehouse user reward rate | | `gcWarehouseDiscountRate` | Float | No | Warehouse discount rate | | `memo` | String | No | Transaction memo | | `categoryId` | UUID | No | Transaction category ID | ### Sample Mutation ```graphql theme={null} mutation { requestGiftCardPurchase( input: { offerId: "ed669305-5e43-40a0-9a25-7a15ed174628" purchaseAmount: 25.00 channel: API fluzpayAmount: 25.00 userCashBalanceId: "b1155504-ad30-4b2f-873d-b8795277b128" } ) { success messageId error { code message } } } ``` ### Sample Response ```json theme={null} { "data": { "requestGiftCardPurchase": { "success": true, "messageId": "1234567890" } } } ``` ## Approve a Request Call `approveApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { approveApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` On approval, Fluz purchases the gift card. ## Decline a Request Call `declineApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { declineApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` No gift card is purchased when a request is declined. # Request Internal Transfer Approval Source: https://docs.fluz.app/features/request-internal-transfer-approval `requestInternalTransfer` Submit a manager approval request to move funds between two spend accounts on the same Fluz account. When approved, Fluz executes the internal transfer. This is different from account-to-account transfers. Use `requestAccountTransfer` when moving funds between Fluz accounts. See [Request Account Transfer Approval](/features/request-account-transfer-approval). ## Scopes | Action | Scope | | ------------------ | --------------------------- | | Create request | `REQUEST_INTERNAL_TRANSFER` | | List requests | `LIST_APPROVALS` | | Approve or decline | `MANAGE_APPROVALS` | ## Webhook Identifiers | Field | Value | | -------------- | -------- | | `approvalType` | `PAYOUT` | | `approvalCode` | `500402` | Webhook events: `APPROVAL_CREATE`, `APPROVAL_APPROVE`, `APPROVAL_DECLINE`, and `APPROVAL_HANDLER_ERROR` on execution failure. > Internal transfers and account transfers share the same `approvalType` and `approvalCode`. Use the original request mutation or inspect the approval payload in your integration to distinguish them. ## Create a Request ### RequestInternalTransferInput | Field | Type | Required | Description | | ------------------------------ | ----- | -------- | --------------------------------------------- | | `amount` | Float | Yes | Amount to transfer. Must be greater than zero | | `sourceUserCashBalanceId` | UUID | Yes | Spend account to withdraw from | | `destinationUserCashBalanceId` | UUID | Yes | Spend account to deposit to | Source and destination must be different spend accounts. ### Sample Mutation ```graphql theme={null} mutation { requestInternalTransfer( input: { amount: 50.00 sourceUserCashBalanceId: "6d1b4b19-deef-42f5-80d7-ec34804ce090" destinationUserCashBalanceId: "bb584e49-d030-4a6e-a5a9-1c34368cbaed" } ) { success messageId error { code message } } } ``` ### Sample Response ```json theme={null} { "data": { "requestInternalTransfer": { "success": true, "messageId": "1234567890" } } } ``` ## Approve a Request Call `approveApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { approveApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` On approval, Fluz transfers funds between the specified spend accounts. ## Decline a Request Call `declineApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { declineApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` No transfer is executed when a request is declined. # Request Reimbursement Approval Source: https://docs.fluz.app/features/request-reimbursement-approval `requestReimbursement` Submit a manager approval request to reimburse a user by moving funds between spend accounts on the same account. When approved, Fluz processes the reimbursement. ## Scopes | Action | Scope | | ------------------ | ----------------------- | | Create request | `REQUEST_REIMBURSEMENT` | | List requests | `LIST_APPROVALS` | | Approve or decline | `MANAGE_APPROVALS` | ## Webhook Identifiers | Field | Value | | -------------- | -------- | | `approvalType` | `PAYOUT` | | `approvalCode` | `500401` | Webhook events: `APPROVAL_CREATE`, `APPROVAL_APPROVE`, `APPROVAL_DECLINE`, and `APPROVAL_HANDLER_ERROR` on execution failure. ## Create a Request ### RequestReimbursementInput | Field | Type | Required | Description | | ------------------------------ | ------ | -------- | ---------------------------------------------------------------------- | | `amount` | Float | Yes | Reimbursement amount. Must be greater than zero | | `sourceUserCashBalanceId` | UUID | Yes | Spend account to withdraw from (typically a company or shared balance) | | `destinationUserCashBalanceId` | UUID | Yes | Spend account to credit (typically the requester's balance) | | `memo` | String | No | Transaction memo | | `categoryId` | UUID | No | Transaction category ID | Source and destination must be different spend accounts. ### Sample Mutation ```graphql theme={null} mutation { requestReimbursement( input: { amount: 75.00 sourceUserCashBalanceId: "6d1b4b19-deef-42f5-80d7-ec34804ce090" destinationUserCashBalanceId: "bb584e49-d030-4a6e-a5a9-1c34368cbaed" memo: "Client dinner reimbursement" } ) { success messageId error { code message } } } ``` ### Sample Response ```json theme={null} { "data": { "requestReimbursement": { "success": true, "messageId": "1234567890" } } } ``` ## Approve a Request Call `approveApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { approveApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` On approval, Fluz processes the reimbursement transfer. ## Decline a Request Call `declineApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { declineApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` No reimbursement is processed when a request is declined. # Overview Source: https://docs.fluz.app/features/spend-accounts A **spend account** is a cash balance account inside Fluz that a user uses to hold, organize, and track funds. Each account has its own nickname, so a user can separate spending by purpose — for example "Team Travel," "Operations Wallet," or "Marketing Budget" — and every account keeps its own running balances. Spend accounts are the wallet that everything else draws from: they hold the funds used to purchase gift cards, fund virtual card transactions, and receive deposits and transfers. **"Spend account" and "cash balance account" are the same thing.** In the API, a spend account is represented by the `UserCashBalance` type, and the operations are named `...UserCashBalance` (e.g., `createUserCashBalance`). This page uses "spend account" throughout. *** ## Balances Every spend account tracks three balances, all returned as strings: | Balance | Field | Description | | ------------- | ---------------------- | ------------------------------------------------------------------ | | **Total** | `totalCashBalance` | The full balance currently held in the account. | | **Available** | `availableCashBalance` | The portion available to spend immediately. | | **Lifetime** | `lifetimeCashBalance` | The cumulative total of all funds ever deposited into the account. | A user can hold **multiple** spend accounts, and one is flagged as the **default** (`isDefault: true`). *** ## What You Can Do | Action | Query / Mutation | Scope | Description | | --------------------------------------------------------- | ------------------------------------------------ | ---------------- | ------------------------------------------------------------------------------------- | | [Create a spend account](/features/create-spend-accounts) | `createUserCashBalance` | `MANAGE_PAYMENT` | Open a new spend account with a custom nickname. | | [Get spend accounts](/features/get-spend-accounts) | `getUserCashBalances` · `getUserCashBalanceById` | `LIST_PAYMENT` | List all spend accounts (with filtering and pagination) or fetch one by ID. | | [Edit a spend account](/features/edit-spend-accounts) | `updateUserCashBalance` | `MANAGE_PAYMENT` | Rename a spend account (nickname, 2–100 characters). | | [Close a spend account](/features/close-spend-accounts) | `closeUserCashBalance` | `MANAGE_PAYMENT` | Close an account, move its remaining balance, and handle any dependent virtual cards. | *** ## Account Lifecycle A spend account moves through a small set of statuses (`UserCashBalanceStatus`): typically `ACTIVE`, and `CLOSED` once closed. Closing an account is more than a status change. When you close a spend account with a remaining available balance, that balance must be moved to another spend account. If any virtual cards are funded by the account being closed, you must either reassign them to a new funding source or lock them as part of the same request. **Closing an account requires you to resolve its balance and dependent cards.** If the account still holds an available balance, provide `transferUserCashBalanceId` to move it. If virtual cards are tied to the account, provide either `newFundingSource` to reassign them or `lockAllVirtualCards` to lock them. See [Close Spend Accounts](/features/close-spend-accounts) for the full input reference. *** ## How Spend Accounts Fit In Spend accounts sit at the center of a user's wallet: * **Fund them** with [Deposit Funds](/features/deposit-from-external-accounts) or move money between them with [Transfer Funds Internally](/features/transfer-between-spend-accounts). * **Spend from them** on [gift cards](/purchase-gift-card) and [virtual cards](/features/virtual-cards). * **Check balances** anytime with [Check Account Balance](/check-account-balance) or the spend account queries above. *** ## Requirements Listing spend accounts requires the `LIST_PAYMENT` scope. Creating, editing, and closing spend accounts require the `MANAGE_PAYMENT` scope. Make sure your user access token carries the correct scope before calling these operations. *** **Want to learn more?** Speak with our experts for more info or to request a demo. # Transactions Overview Source: https://docs.fluz.app/features/transactions-details-overview One ledger for every money movement on the account — which query to reach for, what a transaction record contains, and how to reconcile against your own system. Every movement of money on a Fluz account produces a transaction record: gift card purchases, virtual card authorizations, deposits, withdrawals, internal transfers, wallet-to-wallet sends, bill payments, and cashback. One feed, one shape, one query. Records are **account-level**. There is no user-level filtering — a query returns everything on the account your token is scoped to. *** ## Which query do I want? | You want | Use | Page | | :----------------------------------------- | :--------------------------- | :----------------------------------------------------------------------- | | Everything, filtered any way | `getTransactions` | [Get All Transactions](/features/get-all-transactions) | | Only authorizations that were declined | `getDeclinedTransactions` | [Get Declined Transactions](/features/get-decline-transactions) | | Activity on specific virtual cards | `getVirtualCardTransactions` | [Get Virtual Card Transactions](/features/get-virtual-card-transactions) | | Gift card orders as purchases | `getUserPurchases` | [Get Gift Card Purchases](/features/get-gift-card-purchases) | | Gift cards as assets, with remaining value | `getGiftCards` | [View Gift Cards](/view-gift-card) | **Declines are not a transaction status.** `status` is only ever `PENDING` or `SETTLED` — a declined authorization never becomes a settled transaction, so it won't appear in `getTransactions` at all. If you're debugging "the charge didn't go through," that's `getDeclinedTransactions` and [Decline Codes](/features/decline-codes), not this feed. *** **Field naming is mixed, and you have to match it exactly.** Most fields on the `Transaction` type are snake\_case — `record_id`, `transaction_type`, `created_at`, `cash_balance_available_balance`. But newer additions are camelCase — `memo`, `transactionCategory`, `attachmentUrl`, `connectedAppId`, `connectedAppName`, `expectedClearedDate`. Everything *around* the record is camelCase: the filter input (`createdGte`, `amountGte`, `virtualCardProgram`) and the connection fields (`totalCount`, `hasNextPage`). Introspect the schema before writing queries rather than assuming a convention. → [How the GraphQL API works](/concepts/graphql) *** ## What's in a record Around fifty fields, in six groups. Request only what you need — the response is large if you ask for everything. `record_id`, `account_id`, `user_id`, `user`, `transaction_type`, `channel` (`WEB`, `MOBILE`, `API`), `connectedAppId` and `connectedAppName` — which of your applications initiated it. `amount`, `fee`, `cashback`, `cashback_rate`, `bonus_cashback_rate`, plus two directional fields worth understanding: * `external_funding_source_activity` — the change to external funding sources (bank cards and accounts) * `fluz_balance_activity` — the change to internal Fluz balances Together these tell you whether money entered Fluz, left Fluz, or just moved around inside it. See [below](#balance-snapshots) — every record carries the after-state of every balance. `source` and `destination` as display strings ("Visa \*\*\*\*1234", "Amazon"), `description`, `merchant_id`, `logo_url`, `card_last_four`, `card_display_name`, `virtual_card_program`, `source_type`. Foreign transactions add `original_currency_amount`, `original_currency_code`, and `conversion_rate`. `memo`, `transactionCategory`, `attachmentUrl` — see [Annotating](#annotating-transactions). `reference_id`, `transfer_id`, `liability_id`, `used_user_cash_balance_id`, `descriptor_id` — the fields you reconcile against. See [Reconciliation](#reconciling-against-your-own-system). ### Balance snapshots Every transaction carries the balance of **every** balance type as it stood *after* that transaction was applied. That makes the feed a replayable ledger — you can reconstruct the state of the account at any point in its history without a separate balance-history API. | Field prefix | Balance | Product name | | :------------------------------- | :--------- | :----------------- | | `cash_balance_*` | Cash | Cash balance | | `seat_balance_*` | Rewards | Rewards balance | | `gift_card_prepayment_balance_*` | Prepayment | Prepayment balance | | `reserve_balance_*` | Reserve | Reserve balance | | `other_cash_balance_*` | Other cash | — | `seat_balance_*` is the **rewards** balance. The naming is historical — don't go looking for a separate seat concept. Each comes in `_available_balance` and `_total_balance`. Paired `is_*_affected` booleans (`is_cash_balance_affected`, `is_seat_balance_affected`, `is_gift_card_balance_affected`, `is_reserve_balance_affected`) tell you which balances this transaction actually touched — cheaper to branch on than diffing snapshots. → [Wallet Overview](/features/move-funds-with-external-accounts) for what each balance is. *** ## Filtering `getTransactions` takes a rich `TransactionFilterInput`. The families: | Family | Fields | | :------------------ | :----------------------------------------------------------------------- | | **Record & status** | `recordId`, `status` | | **Amount** | `amount`, `amountGte`, `amountLte`, and the same three for `finalAmount` | | **Cashback** | `cashbackAmount`, `cashbackPercentage`, each with `Gte`/`Lte` | | **Fees** | `feeAmount`, `feeAmountGte`, `feeAmountLte` | | **Dates** | `createdGte`, `createdLte`, `updatedGte`, `updatedLte` — ISO 8601, UTC | | **Merchant** | `merchantId`, `merchant` | | **Properties** | `transactionType`, `channel`, `category` | | **Virtual cards** | `virtualCard`, `virtualCardProgram` | | **Other** | `fundingSource`, `userCashBalanceId`, `referenceId`, `liabilityId` | `amount` is the base amount; `finalAmount` is amount plus fees — the total actually charged. Filter on `finalAmount` when reconciling against what a funding source was debited. **Confirm the accepted values for `transactionType` before relying on it.** The reference page lists human-readable strings (`Add Money`, `Gift Card Purchase`, `Transfer - Out`) in one place and enum-style constants (`GIFT_CARD_PURCHASE`, `DEPOSIT`) in its examples and sample responses. These are not interchangeable. Query a small unfiltered page first and read the actual `transaction_type` values your account returns. ### Pagination and throughput `limit` **maxes out at 20**, and `offset` walks forward. Check `hasNextPage` rather than inferring from a short page; `totalCount` gives the full size of the filtered set. Rate limits are 100 queries per minute per user and 300 per minute per IP, with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers on responses. **Do the arithmetic before building a sync job.** 20 records per query × 100 queries per minute is a ceiling of roughly **2,000 transactions per minute**. An account with 500,000 lifetime transactions takes over four hours to walk end to end. Design for incremental sync: bound every job with `createdGte`/`updatedGte` against your last successful watermark, and never re-walk history you already hold. *** ## Annotating transactions Attach a free-text `memo` (max 255 characters), a `transactionCategory`, and a file to any transaction — either at transaction time on deposits, purchases, and transfers, or afterwards with `updateTransactionMetadata`. Categories are created on first use and reused when the same name comes back. **`attachmentUrl` is a signed URL that expires. Never store it.** Re-fetch the transaction when you need the file. This also breaks naive caching. Settled transactions look immutable, but `memo`, `transactionCategory`, and `attachmentUrl` are all mutable after settlement — so a cached `SETTLED` record will serve stale annotations and a dead attachment link. Cache the financial fields if you like; re-fetch the annotations. → [Add Expense Details](/features/add-expense-details) *** ## Reconciling against your own system Five fields do the joining: | Field | Joins to | | :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `reference_id` | The purchase display ID — the short human-readable Fluz transaction ID (e.g. `1047283`) that also appears on gift card records and in exports, and that Fluz support references | | `transfer_id` | The transfer that produced this record, for wallet-to-wallet movements | | `used_user_cash_balance_id` | Which spend account the money came from — essential for per-budget reporting | | `liability_id` | The bill payment this settles | | `connectedAppId` | Which of your applications initiated it, when several share an account | A workable pattern: 1. **Store `record_id` and `reference_id`** against your own order at the time you create it. Don't try to match on amount and timestamp later. 2. **Sync incrementally on `updatedGte`,** not `createdGte` — a `PENDING` transaction that later settles changes `updated_at`, and a created-date sync will miss the transition. 3. **Expect settlement lag.** ACH withdrawals sit `PENDING` for 1–3 business days; card authorizations settle on their own timeline. `expectedClearedDate` tells you when to look again. 4. **Reconcile balances against snapshots,** not by summing amounts. The `*_available_balance` fields are authoritative and already account for fees, cashback, and pending holds. `externalReferenceId` does **not** appear on transaction records. If you need your own user ID on a movement, join through the account or carry it in `memo` at transaction time. → [Managing External Reference IDs](/managing-external-reference-ids) *** ## Scopes `getTransactions` requires **both** `LIST_PAYMENT` **and** `LIST_PURCHASES`. Missing either returns a `FORBIDDEN` error naming the required scopes. Enable both on your app's Permissions tab before you build — a requested scope that isn't enabled is silently dropped rather than rejected. → [Configure OAuth App](/configure-o-auth-app) *** ## Next steps Full filter, field, and pagination reference. Authorizations that never became transactions. What each decline reason means. Scoped to one or more cards. Orders rather than ledger entries. Memos, categories, and attachments. # Transfer Between Spend Accounts Source: https://docs.fluz.app/features/transfer-between-spend-accounts Move money between two of your own Fluz **spend accounts** — withdrawing from a source spend account and depositing into a destination spend account in a single operation, using the `transferInternalBalance` mutation. **This transfers between your own spend accounts.** A spend account is a cash balance account (the `UserCashBalance` type). This mutation moves funds from one of your spend accounts to another. To send funds to a **different Fluz user**, use [Transfer to Another Fluz Account](/features/transfer-to-another-fluz-wallet) instead. *** ## Transfer Funds Between Spend Accounts To move funds from one spend account to another, use the `transferInternalBalance` mutation. It takes a `TransferInternalBalanceInput` input object identifying the source spend account, the destination spend account, and the amount. ### TransferInternalBalanceInput | Field | Type | Required | Description | | ---------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | amount | Float! | Yes | The amount to transfer from the source spend account to the destination spend account. | | idempotencyKey | String! | Yes | A unique client-generated string to ensure idempotent request processing. If the same key is submitted multiple times, only the first request is processed. | | sourceUserCashBalanceId | UUID! | Yes | The spend account to withdraw funds **from**. | | destinationUserCashBalanceId | UUID! | Yes | The spend account to deposit funds **into**. | ### Both IDs must be your own spend accounts. Use [Get Spend Accounts](/features/get-spend-accounts) to look up the `userCashBalanceId` for each spend account. The source and destination must be two different spend accounts, and the source must have enough available balance to cover the transfer. ### Sample Request GraphQL ```text theme={null} mutation transferInternalBalance($input: TransferInternalBalanceInput!) { transferInternalBalance(input: $input) { cashBalanceDeposit { cashBalanceDepositId depositDisplayId depositAmount depositFee bankAccountId transactionDate clearedDate status expectedClearedDate cashBalanceDepositType cashBalanceSettlements { cashBalanceSettlementId cashBalanceDepositId availabilityType status } } withdraw { withdrawId amount processingFee chargedFee status displayStatus withdrawSource submissionDate createdAt updatedAt transactionLogId externalTransactionId payoutId emailAddress bankAccountId userCashBalanceId seatId } } } ``` ### Example Input In this example, \$6.00 moves from the source spend account into the destination spend account. JSON ```text theme={null} { "idempotencyKey": "1f1df3e7-5d43-4e3d-83de-31922d4aefb7", "amount": 6.00, "sourceUserCashBalanceId": "6d1b4b19-deef-42f5-80d7-ec34804ce090", "destinationUserCashBalanceId": "bb584e49-d030-4a6e-a5a9-1c34368cbaed" } ``` ### Sample Response *** ## Understanding the Response An internal transfer is recorded as two linked movements: a **withdraw** from the source spend account and a **deposit** into the destination spend account. The response returns both. | Field Name | Type | Description | | ------------------ | ------------------ | ---------------------------------------------------- | | cashBalanceDeposit | CashBalanceDeposit | The deposit made into the destination spend account. | | withdraw | Withdraw | The withdrawal taken from the source spend account. | ### cashBalanceDeposit The `cashBalanceDeposit` object contains details about the deposit into the destination spend account. | Field Name | Type | Description | | ---------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------- | | cashBalanceDepositId | UUID! | Unique identifier for the cash balance deposit. | | depositDisplayId | String! | Display identifier for the deposit, intended for user-facing purposes. | | depositAmount | String! | The amount of the deposit. | | depositFee | String | Fee associated with the deposit, if applicable. | | bankCardId | UUID | Identifier of the bank card used for the deposit. | | bankAccountId | UUID | Identifier of the bank account used for the deposit. | | paypalVaultId | UUID | Identifier of the PayPal vault used for the deposit. | | transactionDate | DateTime! | Date and time when the transaction was made. | | clearedDate | DateTime | Date and time when the deposit cleared, if applicable. | | status | CashBalanceDepositStatus! | Current status of the deposit. | | expectedClearedDate | DateTime! | Expected date and time for the deposit to clear. | | cashBalanceDepositType | CashBalanceDepositType! | Type of the cash balance deposit, including CASH\_BALANCE, GIFT\_CARD\_PREPAYMENT, RESERVE\_BALANCE. | | cashBalanceSettlements | \[CashBalanceSettlement] | Settlement time details for the deposit, including status and type. | ### withdraw The `withdraw` object contains details about the withdrawal from the source spend account. | Field Name | Type | Description | | --------------------- | --------------- | ----------------------------------------------------------------- | | withdrawId | UUID! | Unique identifier for the withdrawal. | | amount | String! | The amount withdrawn. | | processingFee | String! | Fee charged for processing the withdrawal. | | chargedFee | String! | Fee charged to the user for the withdrawal. | | status | String! | Internal status of the withdrawal. | | displayStatus | String! | User-friendly display status of the withdrawal. | | withdrawSource | WithdrawSource! | The source balance from which funds were withdrawn. | | submissionDate | DateTime! | Date and time when the withdrawal was submitted. | | createdAt | DateTime! | Date and time when the withdrawal was created. | | updatedAt | DateTime! | Date and time when the withdrawal was last updated. | | transactionLogId | UUID | Identifier of the associated transaction log. | | externalTransactionId | String | External transaction identifier from payment gateway (for ACH). | | payoutId | String | Payout identifier from payment gateway (for PayPal/Venmo). | | emailAddress | String | Email address associated with the withdrawal. | | bankAccountId | UUID | Identifier of the bank account used (if applicable). | | userCashBalanceId | UUID | Identifier of the source spend account funds were withdrawn from. | | seatId | UUID | The seat identifier associated with the account. | **Authorization required** This mutation requires the `MAKE_INTERNAL_TRANSFER` scope. Ensure your access token has been granted this scope before attempting an internal transfer between spend accounts.
# Transfer to Another Fluz Account Source: https://docs.fluz.app/features/transfer-to-another-fluz-wallet Move funds between your application's balance and your users' balances, or between two users. > "Requirement: Your application must be a public app." ## Quick Start Send \$10 from a user to your application: ```graphql theme={null} mutation { createTransfer(input: { idempotencyKey: "550e8400-e29b-41d4-a716-446655440000" amount: 10 }) { success message transferId } } ``` ```text theme={null} Authorization: Bearer ``` Send \$10 from your application to a user: ```graphql theme={null} mutation { createTransfer(input: { idempotencyKey: "550e8400-e29b-41d4-a716-446655440001" amount: 10.00 destination: { accountId: "c3d4e5f6-a7b8-9012-cdef-345678901234" } }) { success message transferId } } ``` ```text theme={null} Authorization: Basic ``` *** ## Authentication Transfers support two authentication methods. The method you choose determines who the sender is. | Method | Sender | Use case | | :----------- | :--------------- | :----------------------------------------------------------------------- | | Bearer token | The user | Collecting payments from users (user → you) or moving funds between user | | Basic Auth | Your application | Disbursing funds to users (you → user) | ### Bearer Token Use a user OAuth token obtained via `generateUserAccessToken` or through the [OAuth refresh flow.](/refresh-o-auth-access-token) The token must include the `MAKE_PAYOUT_TRANSFER_SEND` scope. ### Basic Auth Use your application's Api Key. *** ## Input ### CreateTransferInput | Field | Type | Required | Description | | :------------------ | :------------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | | idempotencyKey | String! | Yes | A unique UUID you generate to prevent duplicate transfers. Resubmitting the same key returns the original result. | | amount | Float! | Yes | Amount to transfer. Must be positive.. | | destination | TransferDestination | Depends | Who receives the funds. **Required for Basic Auth**. For Bearer token, defaults to your application if omitted. | | bankCardId | UUID | No | Fund the transfer from the user's linked bank card instead of their cash balance. Bearer token only.. | | bankAccountId | UUID | No | Fund the transfer via ACH from the user's bank account. Bearer token only.. | | paypalVaultId | UUID | No | Fund the transfer from the user's linked PayPal. Bearer token only.. | | memo | String | No | Free-text note to attach to this transaction. Max 255 characters. | | transactionCategory | String | No | Category label to attach to this transaction. Free-form — categories are created automatically on first use. | | attachmentId | UUID | No | ID of a previously uploaded file (PDF or PNG) to attach to this transaction. Upload the file first using the transaction memo attachment endpoint. | > "Only one funding source can be specified per request. If none is provided, the transfer draws from the sender's cash balance. > > Funding sources are not available with Basic Auth." 📘 See [Add Expense Details](/features/add-expense-details) for full details on uploading attachments and working with memos and categories. ### TransferDirection Identify the recipient by account ID or by the external reference ID you assigned when creating the user. Provide one or the other, not both. | Field | Type | Required | Description | | :------------------ | :----- | :------- | :--------------------------------------------------------------------------------------------------------------------------------- | | accountId | UUID | Yes | The recipient's Fluz account ID.. | | externalReferenceId | String | Yes | The external reference ID you assigned to the user in your system.. | | userCashBalanceId | UUID | Depends | Optional. Target a specific cash balance on the recipient's account. If omitted, the system selects the correct one automatically. | > "The recipient must be a registered user of your application." *** ## Response | Field | Type | Description | | :--------- | :------- | :---------------------------------------------------------- | | success | Boolean! | Whether the transfer completed. | | message | String! | Human-readable result description. | | transferId | UUID | The transfer's unique ID. Present when `success` is `true`. | *** ## Examples ### Collect payment from a user (Bearer) ```graphql theme={null} mutation { createTransfer(input: { idempotencyKey: "550e8400-e29b-41d4-a716-446655440000" amount: 100.00 }) { success message transferId } } ``` ```text theme={null} Authorization: Bearer ``` No destination needed -- defaults to your application. Optionally include `bankCardId`, `bankAccountId`, or `paypalVaultId` to fund from an external source instead of the user's cash balance. ### Disburse funds to a user (Basic Auth) ```graphql theme={null} mutation { createTransfer(input: { idempotencyKey: "550e8400-e29b-41d4-a716-446655440001" amount: 50.00 destination: { accountId: "c3d4e5f6-a7b8-9012-cdef-345678901234" } }) { success message transferId } } ``` ```text theme={null} Authorization: Basic ``` Use `externalReferenceId` instead of `accountId` to identify the recipient by the ID you assigned. ### Transfer between two users (Bearer) ```graphql theme={null} mutation { createTransfer(input: { idempotencyKey: "550e8400-e29b-41d4-a716-446655440002" amount: 25.00 destination: { accountId: "b2c3d4e5-f6a7-8901-bcde-f23456789012" } }) { success message transferId } } ``` ```text theme={null} Authorization: Bearer ``` ### Success response ```json theme={null} { "data": { "createTransfer": { "success": true, "message": "Transfer created successfully.", "transferId": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` *** ## Idempotency Every request must include a unique `idempotencyKey` (a string you generate). If the same key is sent more than once, the API returns the result of the original request without creating a duplicate transfer. Keys are valid for 10 minutes. *** ## Errors Errors follow the standard GraphQL error format: ```json theme={null} { "errors": [ { "message": "Amount must be positive.", "extensions": { "code": "ARG-0001", "name": "InvalidArguments", "status_code": 422 } } ] } ``` ### Common Error | Error | Cause | | :-------------------------------------------------------- | :-------------------------------------------------------------------------------- | | Missing idempotencyKey or amount | Required fields not provided. | | Amount must be positive | Amount is zero or negative. | | Destination is required when using Basic auth | Basic Auth requests must specify a destination. | | Funding sources are not supported when using Basic auth | Remove `bankCardId` / `bankAccountId` / `paypalVaultId` from Basic Auth requests. | | Only one funding source is allowed at a time | Multiple funding sources were provided. Pass only one. | | Provide either accountId or externalReferenceId, not both | Destination has both identifiers. Use one. | | Destination account has not authorized this application | The recipient is not a registered user of your application. | | No user found with this external reference ID | The `externalReferenceId` doesn't match any user in your application. | | User does not have a cash balance configured | The sender or recipient doesn't have a cash balance on the required sponsor bank. | | Basic auth is not supported for personal applications | Upgrade your application to `ACTIVE` status to use Basic Auth transfers. | | Insufficient balance | The sender's cash balance is less than the transfer amount. | # Unlock Virtual Card Source: https://docs.fluz.app/features/unlock-virtual-card The `unlockVirtualCard` mutation allows you to unlock a previously locked virtual card, restoring its ability to process transactions. **Prerequisites:** a user access token with the `EDIT_VIRTUALCARD` scope, and the `virtualCardId` of the card to unlock. ## Arguments * **`input`** (`UnlockVirtualCardInput!`): The input object containing the ID of the virtual card to unlock. ## UnlockVirtualCardInput fields | Field | Type | Description | Required | | --------------- | ------- | --------------------------- | -------- | | `virtualCardId` | `UUID!` | The virtual card to unlock. | Yes | ## Sample mutation ```graphql theme={null} mutation { unlockVirtualCard(input: { virtualCardId: "07df5653-43a8-4532-9881-3ab5857bbe11" }) { virtualCardId unlocked } } ``` ## cURL example ```bash theme={null} curl -X POST \ https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \ -d '{ "query": "mutation { unlockVirtualCard(input: { virtualCardId: \"07df5653-43a8-4532-9881-3ab5857bbe11\" }) { virtualCardId unlocked } }" }' ``` ## Sample response ```json theme={null} { "data": { "unlockVirtualCard": { "virtualCardId": "07df5653-43a8-4532-9881-3ab5857bbe11", "unlocked": true } } } ``` ## Response fields | Field | Type | Description | | --------------- | --------- | -------------------------------------------------------------------------- | | `virtualCardId` | `UUID` | The unique identifier of the virtual card that was targeted for unlocking. | | `unlocked` | `Boolean` | Indicates whether the card was successfully unlocked. | ## Next steps Pre-save and manage the billing addresses your cards are issued against. Confirm spend resumed by pulling the card's transaction history. # View Funding Sources Source: https://docs.fluz.app/features/view-funding-sources Once you've added funding sources to your Fluz account, you can retrieve a list of them with the `getWallet`[query](/api-reference/queries/get-wallet). This query will provide details of all funding sources linked to the user's wallet. ## Sample request ```graphql theme={null} query getWallet { getWallet { bankCards { bankCardId cardType cardholderName lastFourDigits expirationMonth expirationYear cardStatus billingAddressId } bankAccounts { bankAccountId type accountName status achRouting lastFour nickname } paypalAccounts { paypalVaultId status email } blockedPaymentTypes balances { rewardsBalance { availableBalance totalBalance lifetimeBalance } cashBalance { availableBalance totalBalance pendingBalance lifetimeBalance } giftCardCashBalance { availableBalance totalBalance pendingBalance lifetimeBalance } } } } ``` The query response will include user balances and funding source types such as `BankCard`,` BankAccount`and `PayPal`. Here is an example of the response object: ## Sample response ```json theme={null} { "data": { "getWallet": { "bankCards": [ { "bankCardId": "e7979eb7-1727-48ed-8c5a-c56532908c1e", "ownerAccountId": "e7979eb7-1727-48ed-8c5a-c56532908c1e", "addedUserId": "e7979eb7-1727-48ed-8c5a-c56532908c1e", "cardType": "DEBIT", "cardProcessor": "xyz789", "cardholderName": "John Doe", "lastFourDigits": "1234", "expirationMonth": "12", "expirationYear": "25", "cardStatus": "ACTIVE", "billingAddressId": "e7979eb7-1727-48ed-8c5a-c56532908c1e" } ], "bankAccounts": [ { "bankAccountId": "e7979eb7-1727-48ed-8c5a-c56532908c1e", "accountId": "e7979eb7-1727-48ed-8c5a-c56532908c1e", "type": "SAVING", "accountName": "John Doe", "status": "ENABLED", "achRouting": "021000021", "lastFour": "1234", "authChargeBackupPaymentMethod": true, "billingAddressId": "e7979eb7-1727-48ed-8c5a-c56532908c1e", "nickname": "Johnny" } ], "paypalAccounts": [ { "paypalVaultId": "e7979eb7-1727-48ed-8c5a-c56532908c1e", "userId": "e7979eb7-1727-48ed-8c5a-c56532908c1e", "status": "ACTIVE", "email": "abc123@example.com" } ], "blockedPaymentTypes": ["BANK_CARD"], "balances": { "rewardsBalance": "100.00", "cashBalance": "250.00", "giftCardCashBalance": "50.00" } } } } ``` ## Response Fields Explained ### bankCards - BankCardType: The `BankCard` object represents a bank card that has been added by a user to their account. Each object within this array represents a single bank card, including details like the card number (masked), expiration date, cardholder name, and billing information. | Field name | Type | Description | | :--------------- | :------------- | :---------------------------------------------------------------------------- | | bankCardId | UUID! | The unique identifier for the bank card. | | ownerAccountId | UUID! | The unique identifier of the account that owns this bank card. | | addedUserId | UUID! | The unique identifier of the user who added the bank card to the account. | | cardType | BankCardType | The type of the card, such as DEBIT or CREDIT. | | cardProcessor | String | The name or code of the card processor. | | cardholderName | String | The name of the cardholder as it appears on the bank card. | | lastFourDigits | String | The last four digits of the bank card number, used for identification. | | expirationMonth | String | The month the card expires, formatted as MM. | | expirationYear | String | The year the card expires, formatted as YYYY. | | cardStatus | BankCardStatus | The current status of the bank card, such as ACTIVE, INACTIVE, or BLOCKED. | | billingAddressId | UUID | The unique identifier for the billing address associated with this bank card. | ### bankAccounts - \[BankAccount]: The `BankAccount` object is a record of a bank account that a user has added to their account. It stores key information about the bank account, including the account's type, status, details, and associated billing address. | Field name | Type | Description | | :---------------------------- | :--------------- | :--------------------------------------------------------------------------------- | | bankAcountid | UUID! | The unique identifier for the bank account. | | accountId | UUID! | The unique identifier of the account associated with this bank account. | | type | BankAccountType! | The type of bank account, such as CHECKING or SAVING. | | accountName | String! | The name associated with the bank account. | | status | String! | The current status of the bank account, such as ENABLED, DISABLED. | | achRouting | String! | The ACH (Automated Clearing House) routing number for the bank account. | | lastFour | String! | The last four digits of the bank account number, used for identification. | | authChargeBackupPaymentMethod | Boolean! | Indicates whether this bank account is set as a backup payment method for charges. | | billingAddressId | UUID! | The unique identifier for the billing address associated with this bank account. | | nickname | String | An optional nickname given to the bank account for easier identification. | ### paypalAccounts - \[Paypal]: The `Paypal` object represents a PayPal account that a user has added to their account. This object contains essential details about the PayPal account, including its unique identifier, associated user, status, and email address. | Field name | Type | Description | | :------------ | :------------------- | :----------------------------------------------------------------------- | | paypalVaultId | UUID! | The unique identifier for the PayPal account. | | userId | UUID! | The unique identifier of the user who added the PayPal account. | | status | PayPalAccountStatus! | The current status of the PayPal account, such as ACTIVE, INACTIVE, etc. | | email | String! | The email address associated with the PayPal account. | ### blockedPaymentTypes - \[PaymentMethodType]: **Description:** This array lists payment types that are currently blocked for the user.\ **Details:** If a payment type is blocked, it means the user cannot use this method for transactions on the platform. For example, "BANK\_CARD" indicates that the use of bank cards is currently restricted. **PaymentMethodType Enum values:** * **`"BANK_CARD"`**: Indicates that bank cards are blocked. * **`"BANK_ACCOUNT"`**: Indicates that bank accounts are blocked. * **`"PAYPAL"`**: Indicates that PayPal is blocked. * **`"FLUZPAY"`**: Indicated that your Fluz balance is blocked. * **`"APPLE_PAY"`**: Indicated that your Apple Pay wallet is blocked. * **`"GOOGLE_PAY"`**: Indicated that your Google Pay wallet is blocked. ### balances - UserBalances: **Description:** This object contains the user's current balances, including Cash Balance, Rewards Balance, and Fluz prepayment balance. To learn more, you can view ['Check Account Balances'](/check-account-balance).
# Virtual Account Documents Source: https://docs.fluz.app/features/virtual-account-documents Generate payment instructions, paycheck direct-deposit forms, and account status letters as PDFs for a spend account's virtual account number. Three queries return ready-to-share PDF documents for a spend account's [virtual account number](/features/virtual-account-numbers). Each one is generated on demand, returned as a **base64-encoded string**, and is meant to be decoded and either rendered in your UI or offered as a download. All three: * Require the `LIST_PAYMENT` scope. * Take a `userCashBalanceId` identifying the spend account. * Take an optional `virtualAccountNumberId`. **When omitted, the spend account's primary VAN is used.** * Return `SpendAccountPdfDocument!`. *** ## Decoding the Response ```graphql theme={null} query getSpendAccountPaymentInstructions($userCashBalanceId: UUID!) { getSpendAccountPaymentInstructions(userCashBalanceId: $userCashBalanceId) { fileName pdfBase64 } } ``` ```javascript theme={null} const { fileName, pdfBase64 } = data.getSpendAccountPaymentInstructions; // Browser: turn the base64 payload into a downloadable file const bytes = Uint8Array.from(atob(pdfBase64), (c) => c.charCodeAt(0)); const blob = new Blob([bytes], { type: "application/pdf" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = fileName; a.click(); URL.revokeObjectURL(url); ``` These PDFs contain the full, unmasked routing and account number. Do not cache them, log them, or store them outside the user's session. Regenerate on demand instead. *** ## Payment Instructions `getSpendAccountPaymentInstructions` produces a PDF containing the routing and account details needed to fund the virtual account number. Use it when a user needs to tell a customer, vendor, or their own outside bank where to send money. ```graphql theme={null} query paymentInstructions( $userCashBalanceId: UUID! $virtualAccountNumberId: UUID ) { getSpendAccountPaymentInstructions( userCashBalanceId: $userCashBalanceId virtualAccountNumberId: $virtualAccountNumberId ) { fileName pdfBase64 } } ``` | Argument | Type | Required | Description | | ------------------------ | ------- | -------- | ---------------------------------------------------------------- | | `userCashBalanceId` | `UUID!` | Yes | The spend account the funds should land in. | | `virtualAccountNumberId` | `UUID` | No | Specific VAN to document. Defaults to the account's primary VAN. | [API reference](/api-reference/queries/get-spend-account-payment-instructions) *** ## Paycheck Deposit Form `getSpendAccountPaycheckDepositForm` produces a pre-filled direct-deposit authorization form a user can submit to their employer's payroll department. This is the intended path for routing all or part of a paycheck into a Fluz spend account. ```graphql theme={null} query paycheckDepositForm( $userCashBalanceId: UUID! $virtualAccountNumberId: UUID $depositType: PaycheckDepositType! $depositAmount: Float $depositPercentage: Float ) { getSpendAccountPaycheckDepositForm( userCashBalanceId: $userCashBalanceId virtualAccountNumberId: $virtualAccountNumberId depositType: $depositType depositAmount: $depositAmount depositPercentage: $depositPercentage ) { fileName pdfBase64 } } ``` | Argument | Type | Required | Description | | ------------------------ | ---------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `userCashBalanceId` | `UUID!` | Yes | The spend account the paycheck should land in. | | `virtualAccountNumberId` | `UUID` | No | Defaults to the account's primary VAN. | | `depositType` | `PaycheckDepositType!` | Yes | How much of each paycheck the employer should send: `FULL` for the entire check, `FIXED` for a set dollar amount, or `PERCENTAGE` for a share of it. | | `depositAmount` | `Float` | Conditional | **Required when `depositType` is `FIXED`.** | | `depositPercentage` | `Float` | Conditional | **Required when `depositType` is `PERCENTAGE`.** Value from 1–100. | **Validation is enforced server-side.** Sending `depositType: FIXED` without `depositAmount`, or `depositType: PERCENTAGE` without a `depositPercentage` between 1 and 100, returns an error. Validate in your UI before calling. ```json Variables theme={null} { "userCashBalanceId": "9c1f6b2e-4d7a-4c3b-9f11-2a5e8b0d6c74", "depositType": "PERCENTAGE", "depositPercentage": 25 } ``` [API reference](/api-reference/queries/get-spend-account-paycheck-deposit-form) *** ## Account Status Letter `getSpendAccountStatusLetter` produces a letter confirming the account exists and is in good standing — the equivalent of a bank letter. Set `displayBalance` to `true` to include the current balance on the letter; leave it off when the user only needs to prove the account exists. ```graphql theme={null} query statusLetter( $userCashBalanceId: UUID! $virtualAccountNumberId: UUID $displayBalance: Boolean ) { getSpendAccountStatusLetter( userCashBalanceId: $userCashBalanceId virtualAccountNumberId: $virtualAccountNumberId displayBalance: $displayBalance ) { fileName pdfBase64 } } ``` | Argument | Type | Required | Description | | ------------------------ | --------- | -------- | --------------------------------------------------------- | | `userCashBalanceId` | `UUID!` | Yes | The spend account to document. | | `virtualAccountNumberId` | `UUID` | No | Defaults to the account's primary VAN. | | `displayBalance` | `Boolean` | No | Include the account's balance on the letter. Default off. | [API reference](/api-reference/queries/get-spend-account-status-letter) *** ## Choosing the Right Document ```mermaid theme={null} flowchart TD Q{"Who is the user\ngiving this to?"} Q -->|Their employer| A["Paycheck Deposit Form\ngetSpendAccountPaycheckDepositForm"] Q -->|A customer, vendor,\nor outside bank| B["Payment Instructions\ngetSpendAccountPaymentInstructions"] Q -->|A landlord, lender,\nor auditor| C["Account Status Letter\ngetSpendAccountStatusLetter"] ``` *** **Want to learn more?** Speak with our experts for more info or to request a demo. # Virtual Account Numbers Source: https://docs.fluz.app/features/virtual-account-numbers Give each spend account a real routing and account number so external senders can push funds in over RTP, FedNow, Wire, or ACH. A **virtual account number (VAN)** is a real, bank-issued routing number and account number that points at one of a user's [spend accounts](/features/spend-accounts). Anyone outside Fluz — an employer, a bank, a customer, a marketplace — can send money to that routing and account pair, and the funds land in the spend account as a deposit. The VAN does not hold money. The **spend account holds the balance.** A VAN is simply another address that routes into it. **One balance, many addresses.** A spend account can have more than one active virtual account number, and exactly one of them is marked **primary**. Every VAN on the account credits the same balance — they exist so you can separate senders, such as payroll versus a specific customer, without splitting the funds. *** ## How It Fits Together ```mermaid theme={null} flowchart LR subgraph EXT["External Senders"] E1[Employer payroll] E2[User's outside bank] E3[Customer or partner] end subgraph VANS["Virtual Account Numbers"] V1["VAN — primary\nrouting + account"] V2["VAN — secondary\nrouting + account"] end SA[("Spend Account\nUserCashBalance\nholds the balance")] OUT["Gift cards · Virtual cards\nInternal transfers · Withdrawals"] E1 -->|ACH credit| V1 E2 -->|RTP / FedNow| V1 E3 -->|Wire| V2 V1 --> SA V2 --> SA SA --> OUT ``` Money in arrives through a VAN. Money out still leaves the spend account the same way it always has — gift card and virtual card funding, internal transfers, and withdrawals to a linked external account. *** ## Supported Rails Credits sent to a virtual account number are accepted over four rails: | Rail | Direction | Typical availability | Notes | | ---------- | ----------- | -------------------- | --------------------------------------------------------- | | **RTP** | Credit (in) | Near-instant, 24/7 | Sender's bank must participate in The Clearing House RTP. | | **FedNow** | Credit (in) | Near-instant, 24/7 | Sender's bank must be a FedNow participant. | | **Wire** | Credit (in) | Same business day | Domestic wires. Sending bank fees are set by the sender. | | **ACH** | Credit (in) | 1–2 business days | Standard ACH credit, including payroll direct deposit. | **ACH debit (pull) is not supported yet.** A virtual account number can currently only **receive** funds. You cannot use the VAN's routing and account number to originate an ACH debit — a third party cannot pull money out of a spend account using these credentials. Support for ACH debit is **coming soon**. To move money out today, use [Withdraw Funds to an External Account](/features/withdraw-to-external-account) or an [internal transfer](/features/transfer-between-spend-accounts). *** ## What a Credit Looks Like When funds arrive at a virtual account number, Fluz records a standard **deposit** against the destination spend account. The deposit's **funding source is the virtual account** — not a bank card, bank account, or PayPal — because the money originated outside Fluz and was pushed in rather than pulled from a linked payment method. This means: * The credit appears in the same transaction and deposit history as any other deposit. * There is no linked funding source object to reconcile against, and no backup card hold, because nothing was debited from the user. * The deposit is attributable to the specific VAN that received it, so you can distinguish a payroll credit from a customer payment when a spend account has multiple VANs. ```mermaid theme={null} sequenceDiagram participant S as External sender participant B as Receiving bank participant F as Fluz participant SA as Spend account S->>B: Push funds to VAN routing + account B->>F: Credit received (RTP / FedNow / Wire / ACH) F->>F: Match VAN to spend account F->>SA: Post deposit (funding source: virtual account) F-->>S: Funds available to spend ``` *** ## Retrieve a User's Virtual Account Numbers Use `getSpendAccountVirtualAccountNumbers` to list the active VANs on a spend account. It returns every active VAN, one of which is flagged as primary. **Scope required:** `LIST_PAYMENT` ```graphql theme={null} query getSpendAccountVirtualAccountNumbers($userCashBalanceId: UUID!) { getSpendAccountVirtualAccountNumbers(userCashBalanceId: $userCashBalanceId) { virtualAccountNumberId achRoutingNumber accountNumber last4 nickname isPrimary status createdAt } } ``` ```json Variables theme={null} { "userCashBalanceId": "9c1f6b2e-4d7a-4c3b-9f11-2a5e8b0d6c74" } ``` | Argument | Type | Required | Description | | ------------------- | ------- | -------- | --------------------------------------------------------- | | `userCashBalanceId` | `UUID!` | Yes | The spend account whose virtual account numbers you want. | Returns `[SpendAccountVirtualAccountNumber!]!`. See the [type reference](/api-reference/types/spend-account-virtual-account-number) for the full field list. **Displaying account numbers.** A VAN's full account number is sensitive. Mask it in list views and reveal the full value only on an explicit user action, the same way you would treat a card PAN. When a user needs to hand the details to a third party, prefer the generated PDF artifacts described in [Virtual Account Documents](/features/virtual-account-documents) over free-form copy. *** ## Handing the Details to a Third Party Rather than asking a user to transcribe a routing and account number into a payroll portal or send it to a counterparty by email, Fluz generates PDF artifacts on demand: | Document | Query | Use case | | ------------------------- | ------------------------------------ | --------------------------------------------------------------------------- | | **Payment instructions** | `getSpendAccountPaymentInstructions` | Give a customer or partner the details needed to send a wire or ACH credit. | | **Paycheck deposit form** | `getSpendAccountPaycheckDepositForm` | Pre-filled direct deposit form for an employer's payroll department. | | **Account status letter** | `getSpendAccountStatusLetter` | Proof of account, optionally showing the current balance. | All three default to the spend account's **primary** VAN when `virtualAccountNumberId` is omitted, and all three require the `LIST_PAYMENT` scope. See [Virtual Account Documents](/features/virtual-account-documents) for the full reference. *** ## Requirements * The user must have at least one **active spend account**. * Reading virtual account numbers and generating documents both require the `LIST_PAYMENT` scope on the user access token. * Virtual account numbers are provisioned by Fluz. They are not created through the API. *** ## Related The account that actually holds the balance. Payment instructions, deposit forms, and status letters. Pull funds in from a linked bank account or card. How Fluz classifies the money moving in. *** **Want to learn more?** Speak with our experts for more info or to request a demo. # Virtual Card Error Codes Source: https://docs.fluz.app/features/virtual-card-error-codes Every virtual card operation surfaces failures through a `VC-` prefixed code on `extensions.code`. Use this reference to branch on the specific failure rather than the human-readable message. | Error | Code | Message | | :---------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------ | | UNABLE\_TO\_CREATE | VC-0001 | Please try another payment method. If you continue experiencing issues, please contact our support team. | | UNABLE\_TO\_GET\_MERCHANT\_OFFERS | VC-0002 | We are unable to load virtual card merchant offers. If you continue experiencing issues, please contact our support team. | | UNABLE\_TO\_CANCEL | VC-0008 | We are unable to cancel your virtual card. If you continue experiencing issues, please contact our support team. | | UNABLE\_TO\_REVEAL | VC-0009 | We are unable to reveal your virtual card. If you continue experiencing issues, please contact our support team. | | UNABLE\_TO\_GET\_VIRTUAL\_CARDS | VC-0013 | We are unable to get your virtual card history. If you continue experiencing issues, please contact our support team. | | UNABLE\_TO\_GET\_VIRTUAL\_CARD\_BY\_ID | VC-0014 | We are unable to get virtual card by ID. If you continue experiencing issues, please contact our support team. | | UNABLE\_TO\_GET\_VIRTUAL\_CARD\_TRANSACTIONS | VC-0015 | We are unable to get your virtual card transaction history. If you continue experiencing issues, please contact our support team. | | UNABLE\_TO\_GET\_VIRTUAL\_CARD\_PROGRAM\_LIMITS | VC-0016 | We are unable to get your virtual card program limits. If you continue experiencing issues, please contact our support team. | | UNABLE\_TO\_GET\_VIRTUAL\_CARD\_VENDOR\_BALANCE | VC-0017 | We are unable to get the balance of your virtual card's vendor. If you continue experiencing issues, please contact our support team. | | UNABLE\_TO\_GET\_BIN | VC-0018 | This BIN does not exist, please try another BIN. | | UNABLE\_TO\_GET\_VIRTUAL\_CARD\_OFFER | VC-0019 | We are unable to get the virtual card offer. If you continue experiencing issues, please contact our support team. | Address verification failures on card creation return `VC-0025` (`UnableToCreateAuthUser`), which isn't in the table above. See [Address Formatting Requirements](/concepts/address-formatting-requirements) for how to avoid it. ## Next steps Why an individual card transaction was declined at authorization time. Auth, argument, and system errors shared across every endpoint. # Overview Source: https://docs.fluz.app/features/virtual-cards **Pre-Funded Virtual Cards** are network-branded (Visa / Mastercard) cards issued instantly via the Fluz API. They earn cashback at every merchant that accepts the network — and can be layered with gift-card routing at 400+ brands for even higher rewards. ## What Is a Fluz Virtual Card? A Fluz **Pre-Funded Virtual Card** is a network-branded, instantly issued card that lives entirely in software. Unlike a physical card: * **No plastic, no shipping** — the card is available the moment `createVirtualCard` resolves. * **Pre-funded** — the spend limit is reserved at creation time from a Fluz Wallet, gift-card balance, rewards balance, or a linked bank account. * **Scoped** — each card has its own spend limit, duration, lock date, and optional single-use flag, so you control exactly how much can be charged and for how long. * **Full network acceptance** — issued on Mastercard (debit or prepaid) or Visa rails, usable anywhere those networks are accepted online or in-store. Cards earn a base **cashback** on all spend via interchange economics. At supported merchants, card-linked offers can layer on additional returns. ## Card Types | Type | Description | | ----------------------------- | -------------------------------------------------------------------------------------------- | | **Standard Virtual Card** | Works at all merchants on the card network | | **Brand-Locked Virtual Card** | Restricted to a specific merchant — useful for targeted incentive programs or spend controls | Use [`getVirtualCardOffers`](/features/get-card-offers) to enumerate all programs available to your account, including network type (Mastercard / Visa), card type (debit / prepaid), issuing bank, and per-program spend limits. ## Card Lifecycle A card moves through the following states: ```mermaid theme={null} stateDiagram-v2 [*] --> ACTIVE: createVirtualCard ACTIVE --> LOCKED: lockVirtualCard LOCKED --> ACTIVE: unlockVirtualCard ACTIVE --> CLOSED_EXPIRED: lockDate reached, or manually cancelled CLOSED_EXPIRED --> [*] ``` A card set with `lockCardNextUse: true` transitions automatically from `ACTIVE` to `LOCKED` after its first successful authorization — no additional API call required. See [Edit Virtual Card](/features/edit-virtual-card) for how to modify this setting post-creation. ## Required Scopes All virtual card operations require a **User Access Token** generated with the appropriate scopes. The three core scopes are: | Scope | Used By | | -------------------- | ------------------------------------------------------------------------------------------------- | | `CREATE_VIRTUALCARD` | Create cards, fetch offers, check balances, get transactions | | `REVEAL_VIRTUALCARD` | Reveal PAN, CVV, and expiry; also required for `getVirtualCardBalance` alongside `PCI_COMPLIANCE` | | `EDIT_VIRTUALCARD` | Edit, lock, unlock, set PIN | Generate a token with all three scopes before calling any virtual card endpoint: ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Basic " \ -H "Content-Type: application/json" \ -d '{ "query": "mutation generateUserAccessToken($userId: UUID!, $accountId: UUID!, $scopes: [ScopeType!]!) { generateUserAccessToken(userId: $userId, accountId: $accountId, scopes: $scopes) { token scopes } }", "variables": { "userId": "", "accountId": "", "scopes": ["CREATE_VIRTUALCARD", "REVEAL_VIRTUALCARD", "EDIT_VIRTUALCARD"] } }' ``` Pass the returned `token` as `Authorization: Bearer ` on all subsequent calls. See [Refresh an Expired User Access Token](/get-started/refresh-expired-access-token) when you receive a `401`. ## How Funding Works When a card is charged, Fluz draws funds in this priority order by default: 1. The designated **spend account** (`userCashBalanceId`) 2. **Prepaid (gift card) balance** — unless `usePrepaymentBalance: false` 3. **Rewards balance** — unless `useRewardsBalance: false` You can also fund directly from a **linked bank account** by setting `primaryFundingSource: BANK_ACCOUNT`. Full input details and examples are on [Create Virtual Card](/features/create-card). ## Spend Limit Durations The `spendLimit` you set applies per the chosen `spendLimitDuration`: | Duration | Behavior | | ---------- | --------------------------------------------------- | | `LIFETIME` | Applies across the card's entire lifespan (default) | | `DAILY` | Resets each calendar day | | `WEEKLY` | Resets weekly | | `MONTHLY` | Resets on the first of each month | You are **only charged for the amount actually spent** — the spend limit is an authorization ceiling, not a pre-charge. Unused balance stays in your wallet. ## Single-Use Cards Set `lockCardNextUse: true` at creation (or update it via `editVirtualCard`) to lock the card automatically after its first authorization. This is the recommended pattern for: * One-time vendor payments * Single-transaction disbursements * Agentic payment flows where a card should be consumed after one use ## All Virtual Card Operations | Operation | Type | Scope(s) | Page | | --------------------------------- | -------- | --------------------------------------- | ------------------------------------------------------------------------ | | Discover available programs | Query | `CREATE_VIRTUALCARD` | [Get Virtual Card Offers](/features/get-card-offers) | | Pre-save a billing address | Mutation | `CREATE_VIRTUALCARD` | [Add Virtual Card Address](/features/add-billing-address) | | Issue a card | Mutation | `CREATE_VIRTUALCARD` | [Create Virtual Card](/features/create-card) | | Retrieve PAN, CVV, expiry | Mutation | `REVEAL_VIRTUALCARD` | [Reveal Virtual Card](/recipes/reveal-virtual-card) | | Update limit, nickname, lock date | Mutation | `EDIT_VIRTUALCARD` | [Edit Virtual Card](/features/edit-virtual-card) | | Temporarily block spend | Mutation | `EDIT_VIRTUALCARD` | [Lock Virtual Card](/features/lock-virtual-card) | | Re-enable a locked card | Mutation | `EDIT_VIRTUALCARD` | [Unlock Virtual Card](/features/unlock-virtual-card) | | Check remaining balance (batch) | Query | `REVEAL_VIRTUALCARD` + `PCI_COMPLIANCE` | [Get Virtual Card Balance](/recipes/get-virtual-card-balance) | | View transaction history | Query | `CREATE_VIRTUALCARD` | [Get Virtual Card Transactions](/features/get-virtual-card-transactions) | | Issue cards in bulk (async) | Mutation | `CREATE_VIRTUALCARD` | [Bulk Operations](/features/create-bulk-order) | | Distribute cards via hosted link | Mutation | `CREATE_SHARE_LINK` | [Send Hosted Links With Virtual Cards](/features/send-cards) | | Set a PIN | Mutation | `EDIT_VIRTUALCARD` | [Set Virtual Card PIN](/set-virtual-card-pin) | | Push to Apple Pay / Google Pay | Mutation | — | [Digital Wallet Push Provisioning](/digital-wallet-push-provisioning) | | Apply merchant-specific offers | — | — | [Card Linked Offers](/features/card-linked-offers) | | Error reference | — | — | [Virtual Card Error Codes](/features/virtual-card-error-codes) | ## Next steps The full `createVirtualCard` contract — inputs, funding options, spend controls, and examples. # Wallets & Transfers Source: https://docs.fluz.app/features/wallets-overview How money is held and moved on Fluz — balance types, spend accounts, funding in from external sources or virtual account numbers, and reading the ledger. Every dollar on Fluz sits in a **balance**. Money enters a balance from an external funding source or a virtual account number, moves between balances through transfers, and leaves through purchases or withdrawals. This section covers all of it. The most useful thing to understand first: **an account does not have one balance. It has several, and they behave differently.** Some are withdrawable, some are not, and one of them — the spend account — can exist many times over. *** ## The Money Map ```mermaid theme={null} flowchart LR subgraph IN["Money In"] F1["Bank account (ACH)"] F2["Bank card"] F3["PayPal / Apple Pay"] F4["Virtual account number\nRTP · FedNow · Wire · ACH"] F5["Fluz gift card redemption"] F6["Cashback earned"] end subgraph BAL["Balances"] SA1["Spend Account\n'Operations'"] SA2["Spend Account\n'Team Travel'"] RW["Rewards Balance"] GC["Gift Card Balance\nnon-withdrawable"] RS["Reserve Balance\nnon-withdrawable"] end subgraph OUT["Money Out"] O1["Gift card purchases"] O2["Virtual card funding"] O3["Transfers to other\nFluz accounts"] O4["Withdrawals to\nexternal accounts"] end F1 & F2 & F3 --> SA1 F4 --> SA1 F4 --> SA2 F5 --> GC F6 --> RW SA1 & SA2 --> O1 & O2 & O3 & O4 RW --> O1 & O4 GC --> O1 & O2 RS -.->|covers failed settlement| O1 ``` *** ## Balance Types An account can hold up to four kinds of balance. Spend accounts are the only kind a user can have more than one of. | Balance | Withdrawable | What it holds | | -------------------------------- | ------------ | --------------------------------------------------------------------------------------------- | | **Spend account** (cash balance) | Yes | The main working balance. Funds gift cards, virtual cards, transfers, and withdrawals. | | **Rewards balance** | Yes | Cashback and bonus rewards earned on Fluz activity. | | **Gift card balance** | No | Prepaid value usable toward gift card and virtual card purchases only. | | **Reserve balance** | No | Held by Fluz to cover transactions that fail to settle, keeping the account in good standing. | The sum of these is the account's **available Fluz balance** — the total that can be applied toward funding a payment. **The same balance appears under more than one name.** The gift card balance is returned as `giftCardCashBalance` under `getWallet` balances and as `giftCardPrepaymentBalance*` on the `Transaction` type. The rewards balance is `rewardsBalance` under `getWallet` balances and `seatBalance*` on `Transaction`. These are aliases, not separate pots of money. *** ## Spend Accounts Hold the Balance A [spend account](/features/spend-accounts) — `UserCashBalance` in the API — is a named container for cash. A user can open several and give each one a nickname, so funds can be separated by purpose without opening separate Fluz accounts. **Each spend account carries its own independent balance.** Money in one is not spendable from another until it is moved with an [internal transfer](/features/transfer-between-spend-accounts). ```mermaid theme={null} flowchart TD ACC["Fluz Account"] ACC --> RW["Rewards Balance\naccount-level, one only"] ACC --> GC["Gift Card Balance\naccount-level, one only"] ACC --> RS["Reserve Balance\naccount-level, one only"] ACC --> SAS["Spend Accounts\none or many"] SAS --> S1["'Operations'\ntotal · available · lifetime\n+ virtual account numbers"] SAS --> S2["'Team Travel'\ntotal · available · lifetime\n+ virtual account numbers"] SAS --> S3["'Marketing'\ntotal · available · lifetime\n+ virtual account numbers"] S1 <-->|internal transfer| S2 S2 <-->|internal transfer| S3 ``` Every spend account tracks three figures, all returned as strings: | Field | Meaning | | ---------------------- | -------------------------------------------------- | | `totalCashBalance` | The full balance currently held in the account. | | `availableCashBalance` | The portion that can be spent right now. | | `lifetimeCashBalance` | Cumulative total ever deposited into this account. | *** ## Getting Funds In There are two fundamentally different directions money can move into a balance, and the distinction matters for how you build. Your application calls `depositCashBalance` and Fluz pulls funds from a **funding source** the user has already linked: a bank account, bank card, or digital wallet. You control the timing and the amount. An outside party sends money to a **virtual account number** attached to a spend account. Fluz posts it as a deposit when it arrives. You do not control the timing or the amount. | Path | Rails | Initiated by | Lands in | | ----------------------------------------------------------- | ----------------------------- | ------------------ | --------------------------------- | | [Deposit funds](/features/deposit-from-external-accounts) | ACH pull, card, PayPal | Your app | A chosen spend account | | [Virtual account number](/features/virtual-account-numbers) | RTP, FedNow, Wire, ACH credit | An external sender | The spend account behind that VAN | | [Redeem a Fluz gift card](/features/redeem-fluz-gift-card) | — | The user | Gift card balance | | Cashback on qualifying activity | — | Fluz | Rewards balance | **A virtual account number is an address, not a balance.** Each spend account can have one or more virtual account numbers — real routing and account number pairs. Anything sent to them credits that spend account. Multiple VANs on one account all feed the same balance; they exist so you can tell a payroll credit apart from a customer payment. See [Virtual Account Numbers](/features/virtual-account-numbers). *** ## Moving and Removing Funds | Action | Operation | Scope | | ----------------------------------------------------------------------------- | ---------------------- | ---------------- | | [Transfer between spend accounts](/features/transfer-between-spend-accounts) | Internal transfer | `MANAGE_PAYMENT` | | [Transfer to another Fluz account](/features/transfer-to-another-fluz-wallet) | Wallet transfer | `MANAGE_PAYMENT` | | [Look up a transfer recipient](/features/recipient-lookup) | Resolve an `accountId` | — | | [Withdraw to an external account](/features/withdraw-to-external-account) | Withdrawal | `MANAGE_PAYMENT` | **The gift card and reserve balances cannot be withdrawn.** Gift card balance can only be spent on gift card and virtual card purchases. Reserve balance is held by Fluz and is not user-directed. *** ## Reading Balances `getWallet` returns the account's balances in one call, alongside the user's linked funding sources. ```graphql theme={null} query getWallet { getWallet { balances { rewardsBalance { availableBalance totalBalance lifetimeBalance } cashBalance { availableBalance totalBalance pendingBalance lifetimeBalance } giftCardCashBalance { availableBalance totalBalance pendingBalance lifetimeBalance } userCashBalances(paginate: { limit: 10, offset: 0 }) { userCashBalanceId nickname totalCashBalance availableCashBalance lifetimeCashBalance status createdAt } } blockedPaymentTypes } } ``` `userCashBalances` is paginated and returns accounts ordered by creation date, most recent first. To fetch a single spend account, use [`getUserCashBalanceById`](/features/get-spend-accounts). **To show a user's total spendable cash, sum `availableCashBalance` across `userCashBalances`.** Do not add `cashBalance` on top of the individual spend account figures — doing so will overstate the total. The reserve balance is held by Fluz rather than directed by the user. Its current state is visible on each transaction through the `reserveBalanceAvailableBalance` and `reserveBalanceTotalBalance` snapshot fields described below. *** ## Reading the Ledger Balances tell you where things stand. `getTransactions` tells you how they got there. To see the ledger for one specific spend account, filter by its ID. **Scopes required:** `LIST_PAYMENT` **and** `LIST_PURCHASES` ```graphql theme={null} query spendAccountLedger($userCashBalanceId: [UUID], $limit: Int, $offset: Int) { getTransactions( filter: { userCashBalanceId: $userCashBalanceId } paginate: { limit: $limit, offset: $offset } ) { transactions { recordId transactionType amount source destination status usedUserCashBalanceId cashBalanceAvailableBalance createdAt } totalCount hasNextPage } } ``` ```json Variables theme={null} { "userCashBalanceId": ["9c1f6b2e-4d7a-4c3b-9f11-2a5e8b0d6c74"], "limit": 20, "offset": 0 } ``` Every transaction also carries a **balance snapshot** — the state of each balance *after* that transaction was applied — plus flags indicating which balances the transaction touched: | Balance | Snapshot fields | Affected flag | | ------------ | ------------------------------------------------------------------------------------- | --------------------------- | | Spend / cash | `cashBalanceAvailableBalance` · `cashBalanceTotalBalance` | `isCashBalanceAffected` | | Rewards | `seatBalanceAvailableBalance` · `seatBalanceTotalBalance` | `isSeatBalanceAffected` | | Gift card | `giftCardPrepaymentBalanceAvailableBalance` · `giftCardPrepaymentBalanceTotalBalance` | `isGiftCardBalanceAffected` | | Reserve | `reserveBalanceAvailableBalance` · `reserveBalanceTotalBalance` | `isReserveBalanceAffected` | | Other cash | `otherCashBalanceAvailableBalance` · `otherCashBalanceTotalBalance` | — | **Only spend accounts can be filtered by ID.** `TransactionFilterInput` exposes `userCashBalanceId`, but there is no equivalent filter for the rewards, gift card, or reserve balances. To isolate activity on those, retrieve transactions over a date range and filter on the corresponding `is_..._affected` flag. `getTransactions` is capped at **20 records per page**. Check `hasNextPage` and advance `offset` to page through. See [Get All Transactions](/features/get-all-transactions) for the complete filter reference. *** ## Scopes at a Glance | You want to… | Scope | | --------------------------------------- | --------------------------------- | | Read balances, spend accounts, and VANs | `LIST_PAYMENT` | | Read the transaction ledger | `LIST_PAYMENT` + `LIST_PURCHASES` | | Create, edit, or close a spend account | `MANAGE_PAYMENT` | | Deposit, transfer, or withdraw | `MANAGE_PAYMENT` | *** ## Where to Go Next Create, rename, and close the accounts that hold the balance. Receive RTP, FedNow, wire, and ACH credits directly into a spend account. Pull money in from a linked bank account or card. Move money out to an external account. Move balance between a user's own spend accounts. The full ledger, with filtering and pagination. *** **Want to learn more?** Speak with our experts for more info or to request a demo. # Withdraw to External Account Source: https://docs.fluz.app/features/withdraw-to-external-account ## Overview A withdrawal allows users to transfer funds from their Fluz balance to an external account. Users can withdraw from two types of balances: * **Cash Balance** - Funds deposited by the user into their Fluz account * **Rewards Balance** - Cashback earnings accumulated from purchases Fluz supports the following withdrawal methods: | Method | Description | | ----------- | -------------------------------------------- | | `BANK_ACH` | ACH transfer to a linked bank account | | `BANK_CARD` | Push-to-card transfer to a linked debit card | | `PAYPAL` | Transfer to a linked PayPal account | | `VENMO` | Transfer to a linked Venmo account | ## Withdraw Cash Balance ### Sample Request You can initiate a withdrawal with the `withdrawCashBalance` mutation. This mutation transfers funds from a user's Fluz balance to their specified external account. ```json theme={null} { "query": "mutation withdrawCashBalance($input: WithdrawCashBalanceInput!) { withdrawCashBalance(input: $input) { withdraws { withdrawId amount processingFee chargedFee status displayStatus withdrawMethod withdrawSource submissionDate createdAt updatedAt transactionLogId bankAccountId userCashBalanceId seatId } balances { cashBalance { availableBalance totalBalance } rewardsBalance { availableBalance totalBalance } } } }", "variables": { "input": { "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000", "amount": 100.00, "method": "BANK_ACH", "source": "CASH_BALANCE", "bankAccountId": "a578fe07-7165-47b8-b147-2251c99b7fc1", "cashBalanceId": "8d197a56-53df-439b-85cd-bb88dfca9a5f" } } } ``` This mutation requires the `WithdrawCashBalanceInput` input type. Any field marked with an exclamation mark (`!`) in the schema is mandatory and must be included in the request. ### Input Fields | Field Name | Type | Description | | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `idempotencyKey` | `UUID!` | A unique client-generated UUID to ensure the request is processed only once. This prevents duplicate withdrawals if the same request is sent multiple times. | | `amount` | `Float!` | The amount to withdraw. Must be greater than 0. | | `method` | `WithdrawMethods!` | The withdrawal method to use. One of: `PAYPAL`, `BANK_ACH`, `BANK_CARD`, `VENMO`. | | `source` | `WithdrawSource` | The source balance from which to withdraw funds. One of: `CASH_BALANCE`, `REWARDS_BALANCE`. Defaults to `CASH_BALANCE` if not specified. | | `bankAccountId` | `UUID` | Required when `method` is `BANK_ACH`. The identifier of the linked bank account for ACH withdrawals. | | `bankCardId` | `UUID` | Required when `method` is `BANK_CARD`. The identifier of the linked debit card for push-to-card withdrawals. | | `paypalVaultId` | `UUID` | Required when `method` is `PAYPAL`. The identifier of the linked PayPal account. | | `venmoAccountId` | `UUID` | Required when `method` is `VENMO`. The identifier of the linked Venmo account. | | `cashBalanceId` | `UUID` | The identifier of the specific cash balance account to withdraw from. Required when `source` is `CASH_BALANCE`. | ### WithdrawCashBalanceInput ```json theme={null} { "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000", "amount": 100.00, "method": "BANK_ACH", "source": "CASH_BALANCE", "bankAccountId": "a578fe07-7165-47b8-b147-2251c99b7fc1", "bankCardId": null, "paypalVaultId": null, "venmoAccountId": null, "cashBalanceId": "8d197a56-53df-439b-85cd-bb88dfca9a5f" } ``` *** ### Sample Response The response from the `withdrawCashBalance` mutation includes the withdrawal record(s) and the user's updated balances. ```json theme={null} { "data": { "withdrawCashBalance": { "withdraws": [ { "withdrawId": "c4d5e6f7-8901-2345-6789-0abcdef12345", "amount": "100.00", "processingFee": "0.00", "chargedFee": "0.00", "status": "PENDING", "displayStatus": "PROCESSING", "withdrawMethod": "BANK_ACH", "withdrawSource": "CASH_BALANCE", "submissionDate": "2024-01-15T10:30:00Z", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z", "transactionLogId": "f1234567-89ab-cdef-0123-456789abcdef", "bankAccountId": "a578fe07-7165-47b8-b147-2251c99b7fc1", "userCashBalanceId": "8d197a56-53df-439b-85cd-bb88dfca9a5f", "seatId": "e7979eb7-1727-48ed-8c5a-c56532908c1e" } ] } } } ``` ### Response Fields #### Withdraw Object | Field Name | Type | Description | | ----------------------- | ------------------ | --------------------------------------------------------------------------- | | `withdrawId` | `UUID!` | Unique identifier for the withdrawal. | | `amount` | `String!` | The amount withdrawn. | | `processingFee` | `String!` | Fee charged for processing the withdrawal. | | `chargedFee` | `String!` | Fee charged to the user for the withdrawal. | | `status` | `String!` | Internal status of the withdrawal (e.g., `PENDING`, `COMPLETED`, `FAILED`). | | `displayStatus` | `String!` | User-friendly display status (e.g., `PROCESSING`, `COMPLETE`). | | `withdrawMethod` | `WithdrawMethods!` | The withdrawal method used. | | `withdrawSource` | `WithdrawSource!` | The source balance from which funds were withdrawn. | | `submissionDate` | `DateTime!` | Date and time when the withdrawal was submitted. | | `createdAt` | `DateTime!` | Date and time when the withdrawal was created. | | `updatedAt` | `DateTime!` | Date and time when the withdrawal was last updated. | | `transactionLogId` | `UUID` | Identifier of the associated transaction log. | | `externalTransactionId` | `String` | External transaction identifier from payment gateway (for ACH). | | `payoutId` | `String` | Payout identifier from payment gateway (for PayPal/Venmo). | | `emailAddress` | `String` | Email address associated with the withdrawal. | | `bankAccountId` | `UUID` | Identifier of the bank account used (if applicable). | | `userCashBalanceId` | `UUID` | Identifier of the user cash balance account withdrawn from. | | `seatId` | `UUID` | The seat ID associated with the account. | *** ## Required Scope This mutation requires the **`MANAGE_PAYMENT`** scope to be granted to the access token. ```graphql theme={null} generateUserAccessToken( userId: "...", accountId: "...", scopes: [MANAGE_PAYMENT] ) ``` *** ## Withdrawal Methods by Account Type | Method | Required Field | Notes | | ----------- | ---------------- | -------------------------------------------------------------------------------------- | | `BANK_ACH` | `bankAccountId` | Standard ACH transfer. Typically settles in 1-3 business days. No fees. | | `BANK_CARD` | `bankCardId` | Push-to-card instant transfer. Available for eligible debit cards only. May have fees. | | `PAYPAL` | `paypalVaultId` | Transfer to linked PayPal account. May have fees. | | `VENMO` | `venmoAccountId` | Transfer to linked Venmo account. May have fees. | *** ## Error Handling Common error scenarios: | Error Code | Description | | -------------------- | ----------------------------------------------------------------------- | | `MISSING_ARGUMENT` | Required field is missing (e.g., `idempotencyKey`, `amount`, `method`). | | `INVALID_AMOUNT` | Amount is zero or negative. | | `INSUFFICIENT_FUNDS` | User does not have enough balance to complete the withdrawal. | | `WITHDRAW_ERROR` | General withdrawal error. Check the error message for details. | ### Example Error Response ```json theme={null} { "errors": [ { "message": "Missing required argument - require idempotencyKey.", "extensions": { "code": "MISSING_ARGUMENT" } } ] } ``` *** ## Multiple Withdrawals In some cases, a single withdrawal request may result in multiple withdrawal records. This can happen when the withdrawal amount is split across multiple seats (network positions). The response will contain all withdrawal records created. ```json theme={null} { "data": { "withdrawCashBalance": { "withdraws": [ { "withdrawId": "withdraw-1", "amount": "75.00", "seatId": "seat-1" }, { "withdrawId": "withdraw-2", "amount": "25.00", "seatId": "seat-2" } ] } } } ``` *** ## Best Practices 1. **Always use unique idempotency keys** - Generate a new UUID for each withdrawal request to prevent duplicate transactions. 2. **Check balances before withdrawing** - Use the `getWallet` query to verify the user has sufficient funds before initiating a withdrawal. 3. **Handle pending states** - Withdrawals may take time to process. The `status` field will indicate the current state of the withdrawal. 4. **Store transaction references** - Save the `withdrawId` and `transactionLogId` for reconciliation and support purposes. *** ## Changelog ### v1.2.0 - 2024-11-20 **Schema refinements and field cleanup** * Removed `isExpedited` field from `WithdrawCashBalanceInput` - expedited ACH is no longer configurable via the API * Changed `seat_id` field on `Withdraw` type from optional to required (`UUID` → `UUID!`) * Updated description for `BANK_CARD` method to remove "expedited" reference ### v1.1.0 - 2024-10-15 **Added Venmo support and rewards balance withdrawals** * Added `VENMO` to `WithdrawMethods` enum * Added `venmoAccountId` field to `WithdrawCashBalanceInput` * Added `REWARDS_BALANCE` to `WithdrawSource` enum to support withdrawing cashback earnings * Added `seat_id` field to `Withdraw` response type for multi-seat account tracking ### v1.0.0 - 2024-09-01 **Initial release** * Introduced `withdrawCashBalance` mutation with `MAKE_WITHDRAWAL` scope requirement * Added `WithdrawMethods` enum with `PAYPAL`, `BANK_ACH`, and `BANK_CARD` methods * Added `WithdrawSource` enum with `CASH_BALANCE` source * Added `WithdrawCashBalanceInput` input type with idempotency support * Added `Withdraw` response type with full withdrawal record details * Added `WithdrawCashBalanceResponse` type returning withdrawal records and updated balances * Integrated with payout-service for withdrawal processing * Added application action logging for audit trail # Application Scopes Source: https://docs.fluz.app/fluz-dashboard/application-scopes ## Scopes Overview You are able to define what an application can do on an account. This is referred to as application scopes. Scopes represent permissions granted to an application to access specific resources or perform certain actions on behalf of a user. They define the level of access that applications have once they are authenticated. ### App (Global) Scope Grants **Description:** Global scopes are permissions set at an application-wide level by the system administrators through CMS. These define the broadest permissions an application can use. **Purpose:** Ensures that an application cannot request access to more resources than it is allowed at a maximum, regardless of individual user permissions. ### User (Individual) Scope Grants **Description:** User scopes are permissions granted by individual users, specifying what aspects of their data or functionalities an application can access. **Purpose:** Provides users control over their data and limits applications to access only what is necessary and explicitly permitted by the user. ## Valid Scope Requirements This is checked during generateUserAccessToken. For an application's scope to be considered active and valid: Both Grants Must Be Active: The permission must be granted both at the global level (by administrators/system) and at the individual user level. Non-expired Grants: Both the global and user grants must be current and not expired. An expired grant will automatically revoke the application's access to the specified resources. # Application Scopes The following is a list of scopes that are available for any application. | KEY | Usage | | :------------------ | :------------------------------------------------------------------------------- | | LIST\_PAYMENT | Allows access to user's payment methods and account balance. | | LIST\_PURCHASES | Allows access to user's purchase history. | | LIST\_OFFERS | Allows access to offers catalog and inventory data. | | MAKE\_DEPOSIT | Allows making a deposit to the user's balance. | | PURCHASE\_GIFTCARD | Allows purchasing a gift card. | | REVEAL\_GIFTCARD | Allows revealing a gift card code for redemption. | | MANAGE\_PAYMENT | Allows making changes to user's payment methods. | | REVEAL\_VIRTUALCARD | Allows revealing a virtual card details. | | CREATE\_VIRTUALCARD | Allows access to create virtual card. | | EDIT\_VIRTUALCARD | Allows editing, locking, and unlocking virtual cards. | | PCI\_COMPLIANCE | Required alongside other scopes for operations that return PCI-scoped card data. | # Get Application Scopes You can use the `getApplicatonScopes` query to get your account's current application scopes. The query will respond with an array of strings stating what scope is currently granted. ```json theme={null} # Query query getApplicationScopes { getApplicationScopes } # Response { "data": { "getApplicationScopes": ["LIST_PAYMENT"] } } ``` Ensure you have the correct permissions to perform certain mutations and queries with the Fluz API. # Managing Your Application Source: https://docs.fluz.app/fluz-dashboard/managing-your-application ## Fields on an application Each application will have the following fields. * **Display Name** - Must be unique, should be used when requested access from user. * **Developer Contact** - This is the contact information specifically for the application. Recommended to be used for any publicly available applications. * **App Logo** - This is an image file. This will be viewable in the app marketplace. * **App Sub Title** - This is a single one liner to explain the application. It is intended to be short and clear. * **App Description** - This is the overview description of the application. * **App Visibility** - This is your controls to determine if the app will only be available on your Fluz account or if it will be available for the wider Fluz network. ## API Keys Your application will have API keys. You will have a separate API key for both the production and staging environments. ### Production API Keys When you click on any of your applications, you will be able to see the API keys. ### Staging API Keys You will need to log into the staging portal. You can click on the developer tab on your live portal and click 'Open Staging'. The log in credentials are the same as your live account. [https://uni.staging.fluzapp.com/apps-and-integrations](https://uni.staging.fluzapp.com/apps-and-integrations) # Webhooks Source: https://docs.fluz.app/fluz-dashboard/webhooks Webhooks let your application receive real-time notifications when events happen on the Fluz platform. Instead of polling for changes, you register an HTTPS endpoint and Fluz pushes event data to you the moment something occurs — a virtual card transaction, a declined purchase, a completed deposit, a user linking their account via OAuth, and more. This page covers all webhook events, which span multiple platform areas (transaction activity, deposits, widget flows, and OAuth account linking) rather than belonging to any single feature. Webhooks work across all application types on the Fluz platform: private apps operating on your own account, public OAuth apps acting on behalf of other users via API, and embedded widget apps. ## How it works 1. You register a webhook URL on your application in the Developer Portal. 2. You select which event types to listen for (or subscribe to all of them). 3. When a matching event occurs, Fluz sends an HTTP `POST` to your URL with a signed JSON payload. 4. Your server verifies the signature, acknowledges with a `2xx`, and processes the event. ```mermaid theme={null} sequenceDiagram participant Fluz as Fluz participant You as Your Endpoint Fluz->>You: HTTP POST with signed JSON payload You-->>Fluz: 200 OK ``` If your endpoint is unavailable or returns an error, Fluz retries up to **5 times** with exponential backoff before giving up on that delivery. ## Webhooks by app type How events are routed to your app depends on your application model. ### Private apps — your own account Webhooks fire for activity on **your own account**. Any transaction, decline, or deposit on accounts you own triggers webhooks registered on your app. **Common use cases:** notifications when a virtual card transaction is authorized or settled; real-time alerting on declines; monitoring deposits and balance changes on your spend accounts. ### Public apps (OAuth) — acting on behalf of users Webhooks fire for activity on **accounts that have authorized your app**. When a user grants your app access via OAuth, their events are routed to your registered webhooks — provided the user's OAuth grant includes the required scopes. This works whether the user interacts through your direct API integration or an embedded widget; the key requirement is an OAuth relationship between the user and your app. **Common use cases:** knowing when a user has linked (or re-linked) their account to your app; monitoring virtual card spend across connected accounts; real-time decline notifications; tracking deposit completions; receiving KYC status updates. ### Widget apps Widget apps are a specialized public app. Events route the same way (based on the OAuth relationship), and widget apps additionally support widget-specific events like transfer completions and gift card purchases. ## Event types Fluz only delivers an event if your application — **and**, for public/OAuth apps, the individual user's OAuth grant — holds the required scopes. Events with no required scopes (such as `OAUTH_USER_LINKED`) are delivered to any subscribed public/OAuth app. ### Transaction events Cover the full transaction lifecycle. Apply to **all app types**. | Event | Description | Required scopes | | --------------------- | ------------------------------------------------------------------------------- | -------------------------------- | | `TRANSACTION_CREATE` | A new transaction was created (e.g., virtual card purchase, deposit, transfer). | `LIST_PAYMENT`, `LIST_PURCHASES` | | `TRANSACTION_UPDATE` | A transaction's status or amount changed (e.g., settled, adjusted). | `LIST_PAYMENT`, `LIST_PURCHASES` | | `TRANSACTION_DECLINE` | A transaction was declined (e.g., insufficient funds, card controls). | `LIST_PAYMENT`, `LIST_PURCHASES` | ### Deposit events | Event | Description | Required scopes | | ------------------ | ------------------------------------------------------------- | --------------- | | `DEPOSIT_COMPLETE` | A deposit from a funding source to a spend account completed. | `MAKE_DEPOSIT` | ### OAuth events Apply to **public/OAuth and widget apps**. | Event | Description | Required scopes | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | | `OAUTH_USER_LINKED` | A user completed OAuth linking to your application. Fires on both the initial link and subsequent updates (e.g., when the granted scopes change). | *None* | `OAUTH_USER_LINKED` is delivered only to public/OAuth app types — private-app subscribers do not receive it. Because it has no scope requirement, you receive it for every user who links or re-links to your app, regardless of which scopes they grant. Use it to provision or update your local record of the connected user, capture the granted scope set, and map your own user identifier via `externalReferenceId`. ### Widget-specific events Apply to widget and OAuth integrations where users interact through Fluz-embedded flows. | Event | Description | Required scopes | | --------------------------- | ------------------------------------------------- | ------------------------------ | | `WIDGET_KYC_INITIATION` | A user initiated identity verification (KYC). | `VERIFY_KYC` | | `WIDGET_DEPOSIT_COMPLETE` | A transfer from a customer to your app completed. | `MAKE_PAYOUT_TRANSFER_SEND` | | `WIDGET_WITHDRAW_COMPLETE` | A transfer from your app to a customer completed. | `MAKE_PAYOUT_TRANSFER_RECEIVE` | | `WIDGET_PURCHASE_GIFT_CARD` | A gift card purchase completed. | `PURCHASE_GIFTCARD` | > 📘 **Naming note:** `WIDGET_DEPOSIT_COMPLETE` and `WIDGET_WITHDRAW_COMPLETE` describe customer↔app *transfers*; the `DEPOSIT`/`WITHDRAW` wording is historical. Treat "deposit" as "customer → app" and "withdraw" as "app → customer." ## Setting up webhooks ### 1. Open the Developer Portal Go to the [Developer Portal](https://app.fluz.app/for-developers) and select your application. ### 2. Open the Webhooks section * **OAuth apps:** **OAuth** tab → **Webhook URLs**. * **Widget apps:** **Widget** tab → **Webhook URLs**. * **API / private apps:** the **Webhook URLs** section in your app settings. ### 3. Add a webhook URL Click **Add new URL** and enter your HTTPS endpoint (e.g., `https://api.yourapp.com/webhooks/fluz`). ### 4. Select events Choose the event types you want to receive. ### 5. Save Click **Create Webhook**. Your endpoint begins receiving events immediately. ## Managing webhooks * **Multiple endpoints** — you can register more than one webhook URL per application. * **Change subscribed events** — delete the webhook and recreate it with the new event selection. * **Remove a webhook** — click **Remove** next to it. The webhook is archived immediately and stops receiving events. ## Receiving webhooks ### Request format Every webhook is delivered as an HTTP `POST` with these headers: | Header | Description | | ------------------ | --------------------------------------------------------------------------- | | `Content-Type` | Always `application/json`. | | `X-HMAC-Signature` | HMAC-SHA256 signature of the raw JSON body, signed with your app's API key. | | `X-Event-ID` | Unique UUID for this event — use it for deduplication. | The body is a JSON object, and every payload includes an `eventType` field identifying the event. ### Endpoint requirements * **HTTPS only** — plain HTTP endpoints are rejected at registration time. * **Publicly accessible** and able to accept `POST` requests. * **Respond with a `2xx` within 30 seconds.** Non-`2xx` responses or timeouts trigger retries. * **Verify the HMAC signature** on every request. ## Verifying signatures Every delivery includes an `X-HMAC-Signature` header — an HMAC-SHA256 hash of the **raw** JSON body, signed with your application's **API key**. Always verify it before trusting a payload. > ⚠️ **Verify against the raw request body.** Compute the HMAC over the exact bytes Fluz sent — do **not** re-serialize the parsed JSON. Re-stringifying can reorder keys or change whitespace and cause valid signatures to fail. The examples below capture the raw body for this reason. ### Node.js (Express) ```javascript theme={null} const express = require('express'); const crypto = require('crypto'); const app = express(); // Capture the raw body so the HMAC is computed over the exact bytes Fluz sent. app.use('/webhooks/fluz', express.raw({ type: 'application/json' })); function verifyFluzWebhook(rawBody, signature, apiKey) { const expected = crypto.createHmac('sha256', apiKey).update(rawBody).digest('hex'); const received = Buffer.from(signature || '', 'hex'); const computed = Buffer.from(expected, 'hex'); return received.length === computed.length && crypto.timingSafeEqual(received, computed); } app.post('/webhooks/fluz', (req, res) => { const signature = req.headers['x-hmac-signature']; const eventId = req.headers['x-event-id']; // req.body is a Buffer because of express.raw above if (!verifyFluzWebhook(req.body, signature, process.env.FLUZ_API_KEY)) { return res.status(401).send('Invalid signature'); } // Acknowledge immediately, then process asynchronously res.status(200).send('OK'); const event = JSON.parse(req.body.toString('utf8')); handleEvent(eventId, event).catch(err => console.error('Webhook processing error:', err) ); }); ``` ### Python (Flask) ```python theme={null} import hmac, hashlib from flask import Flask, request app = Flask(__name__) def verify_fluz_webhook(raw_body: bytes, signature: str, api_key: str) -> bool: expected = hmac.new(api_key.encode("utf-8"), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature or "") @app.post("/webhooks/fluz") def fluz_webhook(): signature = request.headers.get("X-HMAC-Signature") event_id = request.headers.get("X-Event-ID") # request.get_data() returns the raw bytes — do NOT use request.json for verification if not verify_fluz_webhook(request.get_data(), signature, FLUZ_API_KEY): return "Invalid signature", 401 enqueue(event_id, request.get_json()) # process asynchronously return "OK", 200 ``` ## Responding to webhooks Your endpoint **must**: * Respond with a `2xx` status within 30 seconds. * Return quickly — acknowledge first, then process asynchronously. * Be reachable over HTTPS. Your endpoint **must not**: * Respond with redirects (`3xx`). * Respond with `4xx`/`5xx` for valid webhooks (this triggers retries). ## Retry policy | Behavior | Detail | | ---------------------- | ------------------------------------------- | | Max attempts | 5 | | Schedule | Exponential backoff | | Trigger | Any non-`2xx` response or timeout | | Timeout | 30 seconds per attempt | | After all retries fail | Delivery is abandoned; no further attempts. | New events resume delivery automatically once your endpoint recovers. To re-send events whose retries were already exhausted, contact support with the relevant `X-Event-ID`. ## Idempotency & ordering Webhooks may be delivered **more than once**, and **delivery order is not guaranteed**. * **Deduplicate** using the `X-Event-ID` header. Persist processed IDs (Redis or a database in production) and skip events you've already handled. * **Order by data, not arrival.** If sequence matters, order by payload timestamps (`createdAt`, `updatedAt`, `transactionDateTime`) and event IDs. ```javascript theme={null} const seen = new Set(); // use Redis or a database in production async function handleEvent(eventId, event) { if (seen.has(eventId)) return; // already processed — skip seen.add(eventId); switch (event.eventType) { case 'TRANSACTION_CREATE': await onTransactionCreated(event); break; case 'TRANSACTION_UPDATE': await onTransactionUpdated(event); break; case 'TRANSACTION_DECLINE': await onTransactionDeclined(event); break; case 'DEPOSIT_COMPLETE': await onDepositComplete(event); break; case 'OAUTH_USER_LINKED': await onUserLinked(event); break; case 'WIDGET_KYC_INITIATION': await onKycInitiated(event); break; // ...handle remaining widget events } } ``` ## Identifying the app and user * **User:** `userId` is the Fluz user ID. For OAuth/widget events, `externalReferenceId` maps to *your* user identifier from the OAuth flow. * **App:** transaction payloads include `connectedAppId` and `connectedAppName`. If you route multiple apps to one endpoint, branch on `connectedAppId`. For `OAUTH_USER_LINKED`, the app is identified by `appId`. ## Payload reference Every payload includes an `eventType`. Field availability can vary by event; handlers should ignore unrecognized fields for forward compatibility. > 📘 **A note on `status` values.** For created/updated transactions the `status` field takes one of `PENDING`, `SETTLED`, or `FAILED`. Declined transactions carry a `status` of `DECLINED` (or `FAILED`). Earlier drafts of this page showed `COMPLETED` as a status — that value is not emitted; use `SETTLED` to detect a finalized transaction. ### Transaction created (`TRANSACTION_CREATE`) Fired for any new transaction — virtual card purchases, deposits, transfers, and more. ```json theme={null} { "eventType": "TRANSACTION_CREATE", "recordId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "transactionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "userId": "550e8400-e29b-41d4-a716-446655440000", "transactionType": "PURCHASE", "amount": 42.99, "destination": "Coffee Shop", "source": "Virtual Card", "status": "PENDING", "channel": "VIRTUAL_CARD", "connectedAppId": "your-app-id", "connectedAppName": "Your App", "createdAt": "2025-01-15T10:30:00.000Z", "merchantName": "Coffee Shop", "merchantCity": "New York", "merchantState": "NY", "merchantCountry": "US", "cardLastFour": "1234", "virtualCardId": "vc-uuid-here", "cashbackRate": 0.05 } ``` | Field | Type | Description | | --------------------------------------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------- | | `eventType` | String | Always `TRANSACTION_CREATE`. | | `recordId` | String (UUID) | Unique webhook event record ID. | | `transactionId` | String (UUID) | The transaction ID. | | `accountId` | String (UUID) | The Fluz account ID. | | `userId` | String (UUID) | The Fluz user ID. May be absent on some system-generated transactions. | | `transactionType` | String | Transaction type, e.g. `PURCHASE`, `GIFT_CARD_PURCHASE`, `DEPOSIT`, `WITHDRAWAL`, `TRANSFER`. Treat as an open string. | | `amount` | Number | Transaction amount in USD. | | `destination` | String | Where funds went (e.g., merchant name). | | `source` | String | Funding source (e.g., `Virtual Card`). | | `status` | String (enum) | One of `PENDING`, `SETTLED`, `FAILED`. | | `channel` | String | Origin/channel of the transaction (e.g., `VIRTUAL_CARD`). Treat as an open string. | | `connectedAppId` | String | The app the event is routed to. May be absent for private-app activity. | | `connectedAppName` | String | Display name of the connected app. | | `createdAt` | String (ISO 8601) | When the transaction was created. | | `merchantName` / `merchantCity` / `merchantState` / `merchantCountry` | String | Merchant location details. | | `cardLastFour` | String | Last four digits of the card used. | | `virtualCardId` | String (UUID) | The virtual card ID, if applicable. | | `cashbackRate` | Number | Decimal cashback rate (e.g., `0.05` = 5%). | ### Transaction updated (`TRANSACTION_UPDATE`) Fired when a transaction's status or details change — for example, when a pending authorization settles. ```json theme={null} { "eventType": "TRANSACTION_UPDATE", "recordId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "transactionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "userId": "550e8400-e29b-41d4-a716-446655440000", "transactionType": "PURCHASE", "amount": 42.99, "status": "SETTLED", "connectedAppId": "your-app-id", "connectedAppName": "Your App", "createdAt": "2025-01-15T10:30:00.000Z", "updatedAt": "2025-01-16T08:00:00.000Z" } ``` Fields match `TRANSACTION_CREATE` where present, plus `updatedAt` (ISO 8601) marking when the change occurred. `userId`, `connectedAppId`, and `connectedAppName` are carried through the same way as on `TRANSACTION_CREATE`, so you can identify the user and app consistently across the lifecycle. A `status` transition to `SETTLED` is the signal that a previously pending transaction has finalized. ### Transaction declined (`TRANSACTION_DECLINE`) Fired when a transaction is declined. Includes structured decline reasons your app can act on. ```json theme={null} { "eventType": "TRANSACTION_DECLINE", "transactionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "transactionType": "PURCHASE", "amount": 500.00, "fluzAmount": 500.00, "externalFundingAmount": 0, "currency": "USD", "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "userId": "550e8400-e29b-41d4-a716-446655440000", "status": "DECLINED", "merchantName": "Electronics Store", "cardLastFour": "1234", "virtualCardId": "vc-uuid-here", "isCashBalanceUsed": true, "isPrepaymentBalanceUsed": false, "isRewardsBalanceUsed": false, "isReserveBalanceUsed": false, "transactionDateTime": "2025-01-15T10:30:00.000Z", "declineTitle": "Transaction Declined", "declineReason": "Insufficient funds", "declineDescription": "Your account balance was not sufficient for this transaction.", "declineCategory": "Balance" } ``` | Field | Type | Description | | ------------------------------------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `amount` | Number | Total attempted amount. | | `fluzAmount` | Number | Portion drawn from Fluz balance. | | `externalFundingAmount` | Number | Portion drawn from external funding. | | `currency` | String | ISO currency code (e.g., `USD`). | | `status` | String (enum) | One of `DECLINED`, `FAILED`. | | `userId` | String (UUID) | The Fluz user ID. May be absent on some system-generated transactions. | | `isCashBalanceUsed` / `isPrepaymentBalanceUsed` / `isRewardsBalanceUsed` / `isReserveBalanceUsed` | Boolean | Which balance type(s) the attempt drew from. | | `transactionDateTime` | String (ISO 8601) | When the decline occurred. | | `declineTitle` | String | Short, user-facing title. | | `declineReason` | String | Short reason (e.g., `Insufficient funds`). | | `declineDescription` | String | Longer, user-facing explanation. | | `declineCtaText` | String | Suggested resolution action, when available (e.g., `Add money`). | | `declineCategory` | String | High-level decline grouping. Common values include `Balance`, `Limit`, `Validation`, `Virtual Card`, and `Acquiring`. Treat as an open string and see [Decline Codes](/features/decline-codes) for the full set and per-code detail. | Additional optional fields may be present depending on the transaction (e.g. `merchantId`, `merchantCity`, `merchantState`, `merchantCountry`, `cardDisplayName`, `virtualCardProgram`, `channel`, `bankAccountNickname`, `bankAccountLastFour`). Ignore any you don't use. ### Deposit complete (`DEPOSIT_COMPLETE`) Fired when a deposit from a funding source to a spend account completes. ```json theme={null} { "eventType": "DEPOSIT_COMPLETE", "userId": "550e8400-e29b-41d4-a716-446655440000", "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "externalReferenceId": "your-reference-123", "amount": 50.00 } ``` ### OAuth user linked (`OAUTH_USER_LINKED`) Fired when a user completes OAuth linking to your application — on both the initial link and on subsequent updates (for example, when the user re-authorizes with a different set of scopes). Public/OAuth and widget apps only. No scope is required to receive this event. ```json theme={null} { "eventType": "OAUTH_USER_LINKED", "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "userId": "550e8400-e29b-41d4-a716-446655440000", "appId": "55555555-5555-4555-8555-555555555555", "externalReferenceId": "your-reference-123", "scopes": [ "LIST_PAYMENT", "MAKE_DEPOSIT" ] } ``` | Field | Type | Description | | --------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `eventType` | String | Always `OAUTH_USER_LINKED`. | | `accountId` | String (UUID) | The Fluz account ID that linked to your app. | | `userId` | String (UUID) | The Fluz user ID that linked to your app. | | `appId` | String (UUID) | Your application's ID. | | `externalReferenceId` | String | *Optional.* Your user identifier passed through the OAuth flow. Present for widget app types and any flow that supplied one; may be absent otherwise. | | `scopes` | String\[] | The distinct scope keys granted by this user (e.g. `LIST_PAYMENT`, `MAKE_DEPOSIT`). | Because `OAUTH_USER_LINKED` fires again on re-link/updates, treat it as an upsert: create the connected user on first receipt, and refresh the stored scope set on later deliveries. ### KYC initiation (`WIDGET_KYC_INITIATION`) Fired when a user begins identity verification. Public/widget apps only. ```json theme={null} { "eventType": "WIDGET_KYC_INITIATION", "userId": "550e8400-e29b-41d4-a716-446655440000", "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "externalReferenceId": "your-reference-123" } ``` ### Transfer completed — customer to app (`WIDGET_DEPOSIT_COMPLETE`) Fired when a user transfers funds to your application. ```json theme={null} { "eventType": "WIDGET_DEPOSIT_COMPLETE", "userId": "550e8400-e29b-41d4-a716-446655440000", "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "externalReferenceId": "your-reference-123", "amount": 25.50 } ``` ### Transfer completed — app to customer (`WIDGET_WITHDRAW_COMPLETE`) Fired when your application transfers funds to a user. ```json theme={null} { "eventType": "WIDGET_WITHDRAW_COMPLETE", "userId": "550e8400-e29b-41d4-a716-446655440000", "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "externalReferenceId": "your-reference-123", "amount": 15.00 } ``` ### Gift card purchase (`WIDGET_PURCHASE_GIFT_CARD`) Fired when a gift card purchase completes through the widget. ```json theme={null} { "eventType": "WIDGET_PURCHASE_GIFT_CARD", "userId": "550e8400-e29b-41d4-a716-446655440000", "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "externalReferenceId": "your-reference-123", "amount": 25.00 } ``` **Common widget payload fields:** `userId` (Fluz user ID), `accountId` (Fluz account ID), `externalReferenceId` (your user identifier from the OAuth flow), and `amount` where applicable. ## Best practices * **Respond fast, process later.** Return `200` immediately and handle the event asynchronously to avoid timeouts and unnecessary retries. * **Verify every request.** Validate `X-HMAC-Signature` against the raw body using your API key before processing. * **Deduplicate with event IDs.** Track `X-Event-ID` to handle retried/duplicate deliveries. * **Treat enum-like fields as open strings.** New `transactionType`, `channel`, and `declineCategory` values can appear over time; branch on the values you care about and tolerate unknowns. * **Upsert on `OAUTH_USER_LINKED`.** It can fire more than once per user; refresh the stored scope set each time rather than assuming first-link-only semantics. * **Accept unknown fields.** Payloads may gain fields over time; ignore unrecognized ones rather than failing. * **Monitor your endpoint.** Alert on repeated non-`2xx` responses — after 5 failed attempts an event's delivery is abandoned. ## Troubleshooting | Symptom | Likely cause | Fix | | ------------------------------------------ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Not receiving any webhooks | Endpoint unreachable or returning errors | Confirm the URL is correct, publicly accessible over HTTPS, and returns `2xx`. | | Missing certain event types | App lacks the required scopes | Ensure your app holds the scopes listed for that event. | | Public app not receiving events for a user | User hasn't authorized the required scopes | Confirm the user's OAuth grant includes the needed scopes. | | Not receiving `OAUTH_USER_LINKED` | Subscribed on a private app, or not subscribed at all | This event is public/OAuth only. Subscribe to it (or use catch-all) on an OAuth or widget app. | | Signature verification fails | Wrong API key, or verifying a re-serialized body | Use your app's API key and verify against the raw request body. | | Duplicate deliveries | Retry after a timeout | Implement idempotency using `X-Event-ID`. | | Events stopped coming | 5 consecutive failures exhausted retries | Fix your endpoint; new events resume automatically. Contact support to replay events whose retries were exhausted. | ## Need help? * **Technical issues:** check your endpoint logs and contact support with the `X-Event-ID`. * **Scope questions:** see [Application Scopes](/fluz-dashboard/application-scopes) and [Decline Codes](/features/decline-codes). # Get Best Offer Source: https://docs.fluz.app/get-best-offer getOfferQuote If your goal is to secure the best cashback rate, the `getOfferQuote` [query](/api-reference/queries/get-offer-quote) will help you find the top available offer for a specific merchant, tailored to the criteria you provide. ## **Response:** The response for the getOfferQuote query returns an Offer object, which contains detailed information about the best available offer for the merchant. ## **Arguments:** **`input (GetOfferQuoteInput!)`**:\ The input object that specifies the criteria for retrieving the best offer. This input is required and should include the merchant's slug and other relevant details. **`GetOfferQuoteInput`** **Fields:** **`merchantSlug (String!)`**:\ The unique slug identifier for the merchant. This field is required. This slug is used to specify the merchant for which the offer is being requested. You can use the `getMerchants` query described earlier to get your merchant's slug. **`denomination (Float!)`**:\ The purchase amount for which the offer is being requested. This field is also required and the denomination value will determine what offers are available. **`paymentMethod (PaymentMethodType):`**\ The payment method that will be used for the purchase. The default is `FLUZPAY`, your available Fluz balance, but other payment methods can be specified depending on the offer's availability. These include: * `BANK_CARD` * `BANK_ACCOUNT` * `PAYPAL` * `FLUZPAY` * `APPLE_PAY` * `GOOGLE_PAY` ## Example Usage: Here’s how you might structure a getOfferQuote query: ```graphql theme={null} query getOfferQuote($input: GetOfferQuoteInput!) { getOfferQuote(input: $input) { offeringMerchantId offerId type hasStockInfo offerRates { ...OfferRateFragment } denominationsType termsAndConditions stockInfo { ... on StockInfoVariableType { __typename description maxDenomination minDenomination } ... on StockInfoFixedType { __typename denomination availableStock } } } } ``` ### Response Details The `Offer` object represents the offer details associated with the merchant. **`offeringMerchantId (UUID)`** : The unique id for the offering merchant. **`offerId (UUID!)`**: The unique id for the offer. **`type (OfferType)`**: Indicates the type of the offer. This could be a standard gift card offer or an exclusive rate offer. \[Example: GIFT\_CARD\_OFFER, EXCLUSIVE\_RATE\_OFFER]. **`hasStockInfo (Boolean)`**: Specifies whether the offer includes stock information. If true, the stockInfo field will be populated with available denominations. **`denominationsType (OfferDenominationType)`**: Defines the type of denominations available for the offer. This field is used to specify how the offer's value is structured, such as fixed or variable denominations. **`termsAndConditions (String)`**: The terms and conditions text for this offer (legal language, restrictions, expiration policies). Use this to surface T\&C to end users before they purchase a gift card. **`stockInfo ([StockInfoType]!)`** StockInfo is a list of available denominations for a specific offer. This field object is only available if the offer's hasStockInfo is true. This requires Fluz to confirm the inventory. Keep in mind, response time varies by vendors. The stockInfo field will conditionally return info depending on the `VARIABLE` or `FIXED` type. To query for stockInfo, you will need to utilize an inline fragment using the `... on`keyword. This will return either `StockInfoVariableType` if the denomination type is `VARIABLE` or `StockInfoFixedType` if the denomination type is `FIXED`. Sample Response: ```json theme={null} { "data": { "getOfferQuote": { "offeringMerchantId": "7b4280a6-dadc-4cf3-a99a-70d291e43c1c", "offerId": "7b4280a6-dadc-4cf3-a99a-70d291e43c1c", "type": "GIFT_CARD_OFFER", "hasStockInfo": true, "offerRates": [], "denominationsType": "VARIABLE", "termsAndConditions": "Card valid only for purchases on the merchant's U.S. website. Cannot be used to purchase other gift cards. Not redeemable for cash except where required by law.", "stockInfo": [ { "__typename": "StockInfoVariableType", "description": "Available denominations range from $5 to $200", "maxDenomination": "200", "minDenomination": "5" } ] } } } ``` For more detail, refer to the [How the GraphQL API works](/concepts/graphql) article. The `OfferRate` object represents the specific reward rates and conditions associated with an offer. This object contains detailed information about the rewards that users can receive when they take advantage of an offer, as well as the conditions under which these rewards apply: **`maxUserRewardValue (Float)`**:\ The maximum value cashback that a user can receive. This field defines the upper limit of the reward amount that a user can earn for a specific offer. **`cashbackVoucherRewardValue (Float)`**:\ The cashback value after a boost is applied to the offer. Typically this is a 25% cashback boost on the first \$10 of your spend. **`boostRewardValue (Float)`**:\ When the cashback value on an offer has been increased, it will display here. These offers are an additional incentive that may be offered to users for a period of time. **`displayBoostReward (Boolean)`**:\ Indicates whether the boost reward should be displayed to the user. This field is used to control the visibility of the boost reward in the user interface. **`denominations ([String])`**:\ An array of denominations that are eligible for the offer. These denominations represent specific amounts (e.g., "10", "25", "50") that are applicable for the offer, defining the monetary values that qualify for the rewards. **`allowedPaymentMethods ([String])`**:\ An array of payment methods that are allowed for this offer. This field specifies which payment methods (e.g., "CREDIT\_CARD", "PAYPAL") can be used by the user to take advantage of the offer. > 🚧 Cashback rates are subject to change. > > We do our best to always give our customers the best offers available. This means that our rates change regularly. Always confirm the rate before making a purchase.
# Get Catalog Source: https://docs.fluz.app/get-catalog getMerchants Use the `getMerchants` GraphQL query to retrieve the current list of available merchants and their offers from the Fluz catalog. This is the primary way to discover which merchants are available and what kinds of deals they offer. You can refine the results using optional input arguments: * `name`: Filter merchants by a specific name. * `paginate`: Control the number of results per page (`limit`) and the starting point (`offset`). * `offerTypes`: Specify whether you want `giftCardOffer`s, `cardLinkedOffer`s, or both using boolean flags (e.g., `{ giftCardOffer: true, cardLinkedOffer: false }`). * `filterBy`: An object used to apply specific filters to the offers within each merchant. If a merchant has no offers remaining after filtering, it is excluded from the final results.\ Available filters: 1. `deliveryFormat`: Filters gift card offers by their delivery method.\ Allowed values: `URL`, `CODES`, `PIN_AS_CODE`, `PIN_WITH_URL`. > 📘 Retrieving the entire merchant catalog without filters can be time-consuming. We recommend fetching the full, unfiltered catalog only once per day. For more frequent updates or specific lookups, use the `name` or `offerTypes` filters. > 📘 **Notes on pagination:** > > The default and max limit for pagination is 20. The result will not necessarily return the amount that you define as the limit. For example, you may request a limit of 10, but the response may only have 7 results. > > When an offset is included, remember to include the limit amount in your offset calculation for the next batch. If no limit is specified, you will need to offset by the default limit of 20 (e.g. offset+20). > 📘 **Fetching the full merchant catalog:** > > **If you want all available merchants, you must paginate until the API returns an empty array.** > > Steps to fetch the full catalog: > > 1. Call getMerchants (offset = 0, limit = 20). > 2. Append the returned merchants to your local list. > 3. Increase the offset by your limit (offset += 20). > 4. Repeat the request. > 5. Stop only when the API returns an empty array (\[]). ## Basic query structure Here’s a flexible query structure using variables. You can adjust the `offerTypes` variable to fetch the specific offers you need. This example requests only the most common, basic fields. **Sample request:** ```graphql theme={null} # Define the query with variables query GetMerchantCatalogBasic( $paginate: OffsetInput, $offerTypes: OfferTypesInput, # Controls which offer types are returned $filterBy: FilterByInput ) { getMerchants( paginate: $paginate, offerTypes: $offerTypes, filterBy: $filterBy ) { # --- Common Merchant Fields --- merchantId name slug logoUrl # Merchant logo image URL faceplateUrl # Rectangular card art/faceplate image URL # --- Basic Offer Info (Common to all types) --- offers { offeringMerchantId offerId type # Crucial field to identify offer type barcodeType # Barcode format (NONE, C128, PDF417, QRCODE) # Request offer-specific fields like offerRates, stockInfo, # or cloDetails here based on expected types. # See subpages for details. } } } # Example Variables (Fetch Gift Cards Only): # { # "paginate": { "limit": 20, "offset": 0 }, # "offerTypes": { "giftCardOffer": true, "cardLinkedOffer": false } # } # Example Variables (Fetch CLOs Only): # { # "paginate": { "limit": 20, "offset": 0 }, # "offerTypes": { "giftCardOffer": false, "cardLinkedOffer": true } # } # Example Variables (Filter by deliveryFormat: CODES): # { # "paginate": { "limit": 20, "offset": 0 }, # "filterBy": { "deliveryFormat": URL } # } ``` **Sample response structure (gift card example):** ```json json theme={null} { "data": { "getMerchants": [ { "merchantId": "123e4567-e89b-12d3-a456-426614174000", "name": "Example Merchant", "slug": "example-merchant", "logoUrl": "https://storage.googleapis.com/.../example-merchant-logo.jpg", "faceplateUrl": "https://storage.googleapis.com/.../example-merchant-faceplate.png", "offers": [ { "offeringMerchantId": "3c7b4d5e-6f7g-8h9i-10jk-11l12m13n14o", "offerId": "1a2b3c4d-5e6f-7g8h-9i10-jk11l12m13n14", "type": "GIFT_CARD_OFFER", "barcodeType": "C128" // Other fields like offerRates, stockInfo would be here // if requested and applicable. } ] } ] } } ``` ## Response details The `Merchant` object returned by the `getMerchants` query includes the following fields: | Field name | Type | Description | | :----------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | merchantId | UUID! | The unique identifier for the merchant. This ID is essential for referencing the merchant in other API queries or transactions. | | name | String | The name of the merchant. This is the display name that users will recognize when selecting a merchant for gift card usage or virtual card charges. | | slug | String | A URL-friendly version of the merchant's name. The slug is often used in web addresses or as a reference in the user interface for ease of use. | | logoUrl | String | The URL of the merchant's logo image. Typically a square (1:1) image suitable for display in merchant lists and headers. Returns null if not available. | | faceplateUrl | String | The URL of the merchant's rectangular faceplate/card art image. This is ideal for displaying when no barcode is present (barcodeType === "NONE") or as a card background. Returns null if not available. | | offers | \[Offer] | An array of `Offer` objects that represent the promotions, discounts, or deals available at the merchant. Each Offer object provides additional details such as the discount amount, expiration date, and any applicable conditions. | Common Offer Fields (within offers array): These fields are present in every `Offer` object, regardless of its type. | Field Name | Type | Description | | :----------------- | :----------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | offeringMerchantId | UUID! | A unique identifier for the merchant offering the specific deal or discount. This is a required field and must be a valid UUID. | | offerId | UUID! | A unique identifier for the offer itself. | | type | String! | Indicates the type of the offer. This could be a standard gift card offer or an exclusive rate offer. | | deliveryFormat | DeliveryFormatType | Specifies the method by which the offer will be delivered to the user. This determines how the user will receive and redeem the offer. For card-linked offers, this field will be null as the offer is automatically applied. | | barcodeType | BarcodeTypeEnum | Specifies the barcode format used for this offer. Possible values: NONE (no barcode), C128 (Code 128), PDF417, QRCODE. Use this to determine whether to render a barcode or display alternative visuals like faceplateUrl. | ## Offer type specific details The real details of an offer depend on its `type`. You need to request and interpret different fields based on the offer type: * **For gift card offers (`GIFT_CARD_OFFER`, `EXCLUSIVE_RATE_OFFER`):** * Key fields include `offerRates`, `stockInfo`, `hasStockInfo`, `denominationsType`, `allowedPaymentMethods`. * See the [**Gift Card Offers**](/get-gift-card-offers) subpage for a detailed explanation and query examples. * **For card linked offers (`CARD_LINKED_OFFER`):** * The primary field is `cloDetails`, which contains rates, periods, and conditions. * See the [**Card Linked Offers**](/features/card-linked-offers) subpage for a detailed explanation and query examples.
# Get Gift Card Offers Source: https://docs.fluz.app/get-gift-card-offers **Scope Required:** `LIST_OFFERS` ([API Reference Link](/api-reference/queries/get-merchants)) This page details how to retrieve and understand Gift Card Offers using the `getMerchants` query. Gift Card offers typically include types like `GIFT_CARD_OFFER` and `EXCLUSIVE_RATE_OFFER`. To fetch *only* Gift Card offers, use the `offerTypes` input argument set to `{ giftCardOffer: true, cardLinkedOffer: false }`. ## Detailed Query for Gift Card Offers This query requests fields specifically relevant to Gift Cards, including reward rates (`offerRates`) and stock availability (`stockInfo`). **Sample Request:** ```graphql theme={null} # Define the query with variables query GetGiftCardMerchants( $name: String, $paginate: OffsetInput, $offerTypes: OfferTypesInput # Define the input type for offerTypes (optional) ) { getMerchants( name: $name, paginate: $paginate, offerTypes: $offerTypes # Pass the variable here (optional) ) { merchantId name slug logoUrl # Merchant logo image URL faceplateUrl # Rectangular card art/faceplate image URL shortDescription # Short marketing description of the merchant offers { offerId exclusiveRateId # Optional, only returns for type = EXCLUSIVE_RATE_OFFER type deliveryFormat # How the gift card will be delivered (URL, CODES, etc.) barcodeType # Barcode format (NONE, C128, PDF417, QRCODE) hasStockInfo denominationsType # FIXED or VARIABLE termsAndConditions # Offer T&C text (legal language, restrictions, expiration) # --- Offer Rates (Relevant for Gift Cards) --- offerRates { maxUserRewardValue cashbackVoucherRewardValue boostRewardValue displayBoostReward denominations allowedPaymentMethods } # --- Stock Info (Relevant for Gift Cards) --- stockInfo { ... on StockInfoVariableType { __typename description maxDenomination minDenomination } ... on StockInfoFixedType { __typename denomination availableStock } } # --- CLO Details (Will be null for Gift Cards) --- # cloDetails { currentRateType } # Can query, but expect null } } } # Example Variables to pass with the query: # { # "paginate": { "limit": 20, "offset": 0 }, # "offerTypes": { "giftCardOffer": true, "cardLinkedOffer": false } # } ``` Please note that due to rate-limiting, you might need to paginate and call the merchants list a few times. ## Sample Response (When Filtering for Gift Card Offers): The response will contain merchants, but the offers array for each merchant will only include offers matching the offerTypes filter (in this case, Gift Card Offers). ```json JSON theme={null} { "data": { "getMerchants": [ { "merchantId": "123e4567-e89b-12d3-a456-426614174000", "name": "Best Buy", "slug": "best-buy", "logoUrl": "https://storage.googleapis.com/.../best-buy-logo.jpg", "faceplateUrl": "https://storage.googleapis.com/.../best-buy-faceplate.png", "shortDescription": "America's leading destination for tech, gadgets, and home electronics.", "offers": [ { "offeringMerchantId": "255f8245-02c7-4817-901e-15fe265f6968", "offerId": "1a2b3c4d-5e6f-7g8h-9i10-jk11l12m13n14", "exclusiveRateId": null, "type": "GIFT_CARD_OFFER", "deliveryFormat": "CODES", "barcodeType": "C128", "hasStockInfo": true, "offerRates": [ { "maxUserRewardValue": 50.0, "cashbackVoucherRewardValue": 5.0, "boostRewardValue": 10.0, "displayBoostReward": true, "denominations": ["10", "500"], "allowedPaymentMethods": ["CREDIT_CARD", "DEBIT_CARD"] } ], "denominationsType": "FIXED", "termsAndConditions": "Card valid only at Best Buy U.S. retail stores and BestBuy.com. Cannot be used to purchase other gift cards. Not redeemable for cash except where required by law.", "stockInfo": [ { "__typename": "StockInfoFixedType", "denomination": 500, "availableStock": 200 } ] } ] }, { "merchantId": "223e4567-e89b-12d3-a456-426614174001", "name": "H&M", "slug": "h&m", "logoUrl": "https://storage.googleapis.com/.../hm-logo.jpg", "faceplateUrl": null, "shortDescription": "Affordable fashion for women, men, and kids — from staples to seasonal collections.", "offers": [ { "offeringMerchantId": "3c7b4d5e-6f7g-8h9i-10jk-11l12m13n14o", "offerId": "2a2b3c4d-5e6f-7g8h-9i10-jk11l12m13n15", "exclusiveRateId": "0c8e6beb-5d62-4a0c-ae94-b889395f1e2d", "type": "EXCLUSIVE_RATE_OFFER", "deliveryFormat": "URL", "barcodeType": "NONE", "hasStockInfo": false, "offerRates": [ { "maxUserRewardValue": 30.0, "cashbackVoucherRewardValue": 3.0, "boostRewardValue": 8.0, "displayBoostReward": false, "denominations": ["20", "50", "100"], "allowedPaymentMethods": ["CREDIT_CARD", "PAYPAL"] } ], "denominationsType": "FIXED", "termsAndConditions": "Redeemable online at hm.com or any H&M retail store in the U.S. Cannot be redeemed for cash except where required by law.", "stockInfo": [] } ] } ] } } ``` ### Understanding the 'Offers' array: The offers field within the `Merchant` object contains an array of `Offer` objects. The offer object contains information about specific offers available from merchants in the Fluz catalog. Below are the fields included:
Field Name Type Description
offeringMerchantId UUID! A unique identifier for the merchant offering the specific deal or discount. This is a required field and must be a valid UUID.
offerId UUID! A unique identifier for the offer itself.
exclusiveRateId UUID The exclusive\_rate\_id for the offer. Only returned for type = EXCLUSIVE\_RATE\_OFFER. This value can be passed in to purchaseGiftCard to specify the exclusive rate you want to purchase with.
type String! Indicates the type of the offer. This could be a standard gift card offer or an exclusive rate offer.
deliveryFormat DeliveryFormatType Specifies how the gift card will be delivered to the user. Possible values: URL, CODES, PIN\_AS\_CODE, PIN\_WITH\_URL, CODE\_WITH\_PREFIX. This determines how the user will receive and redeem the gift card.
barcodeType BarcodeTypeEnum Specifies the barcode format for this offer. Possible values: NONE (no barcode - consider displaying faceplateUrl instead), C128 (Code 128), PDF417, QRCODE. Use this to determine whether to render a barcode in your UI.
hasStockInfo Boolean Specifies whether the offer includes stock information. If true, the stockInfo field will be populated with available denominations.
offerRates \[OfferRate] Represents the specific reward rates and conditions associated with an offer.
denominationsType OfferDenominationType Defines the type of denominations available for the offer. This field is used to specify how the offer's value is structured, such as fixed or variable denominations.
termsAndConditions String The terms and conditions text for this offer (legal language, restrictions, expiration policies). Use this to surface T\&C to end users before they purchase a gift card.
stockInfo \[StockInfoType]! StockInfo is a list of available denominations for a specific offer. It's only available if the offer hasStockInfo. This requires Fluz to confirm the inventory, response time varies by vendors. The stockInfo field will conditionally return info depending on the `VARIABLE` or `FIXED` type. To query for stockInfo, you will need to utilize an inline fragment using the `... on`keyword. This will return `StockInfoVariableType` if the denomination type is `VARIABLE` or `StockInfoFixedType` if the denomination type is `FIXED`. For more detail, refer to the [How the GraphQL API works](/concepts/graphql) article.
### Understanding the 'Offer rate': The `OfferRate` object represents the specific reward rates and conditions associated with an offer. This object contains detailed information about the rewards that users can receive when they take advantage of an offer, as well as the conditions under which these rewards apply: | Field Name | Type | Description | | :------------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | maxUserRewardValue | Float | The maximum value cashback that a user can receive. This field defines the upper limit of the reward amount that a user can earn for a specific offer. | | cashbackVoucherRewardValue | Float | The cashback value after a boost is applied to the offer. Typically this is a 25% cashback boost on the first \$10 of your spend. | | boostRewardValue | Float | When the cashback value is increased on an offer, it will display here. These offers are an additional incentive that may be offered to users for a period of time. | | displayBoostReward | Boolean | Indicates whether the boost reward should be displayed to the user. This field is used to control the visibility of the boost reward in the user interface. | | denominations | \[String] | An array of denominations that are eligible for the offer. These denominations represent specific amounts (e.g., "10", "25", "50") that are applicable for the offer, defining the monetary values that qualify for the rewards. | | allowedPaymentMethods | \[String] | An array of payment methods that are allowed for this offer. This field specifies which payment methods (e.g., "CREDIT\_CARD", "PAYPAL") can be used by the user to take advantage of the offer. |
# Inventory on Stocked Offers Source: https://docs.fluz.app/get-inventory Some of the denominations on select gift card brands are stocked. Other merchants only offer variable value gift cards that are generated in real time. If a gift card offer is stocked, that is generally a better rate or higher purchasing limits on that specific offer. It will be specific denominations that are generally stocked on a gift card brand. In order to see our inventory you will need to call the get stock information API call. That will allow you to see the exact quantity available for those specific denominations. Some merchants have both variable and stocked gift card offers. Please determine which type of offer you are looking to purchase based on pricing and total volumes. Using the `Offer` object from either the `getOfferQuote` or `getMerchants` query, you can determine stock availability. * If **`hasStockInfo`** is true, the `stockInfo` field will display a list of all available denominations that are currently in stock. * If **`hasStockInfo`** is false, any value denominations in the `offerRates` `denominations` field will be in stock. * If the **`denominationsType`** is `"VARIABLE"`, you can purchase any value between the minimum and maximum denomination. * **`stockInfo`** is a list of available denominations for a specific offer. This field object is only available if the offer's hasStockInfo is true. This requires Fluz to confirm the inventory. Keep in mind, the response time varies by vendors. The stockInfo field will conditionally return information depending on the `VARIABLE` or `FIXED` type. To query for stockInfo, you will need to utilize an inline fragment using the `... on`keyword. This will return `StockInfoVariableType` if the denomination type is `VARIABLE` or `StockInfoFixedType` if the denomination type is `FIXED`. In your getOfferQuote and getMerchants queries, you will have to include both StockInfoVariableType and StockInfoFixedType fragments. For more detail, refer to the [How the GraphQL API works](/concepts/graphql) article. ```json JSON theme={null} "offerRates":[{ "denominations": ["10","1000"] }] ``` * If the **`denominationsType`** is `"FIXED"`, you will either: * If `hasStockInfo` is true, purchase a specific value in the `stockInfo` field. It will return either `StockInfoVariableType` or `StockInfoFixedType` conditionally depending on the `denominationsType`.
```json StockInfoVariableType theme={null} "stockInfo": [ { "__typename": "StockInfoVariableType", "description": "Available denominations range from $5 to $200", "maxDenomination": "200", "minDenomination": "5" } ] , ``` ```json StockInfoFixedType theme={null} "stockInfo": [ { "__typename": "StockInfoFixedType", "availableStock": "200", "denomination": "5" } ] ``` * If `hasStockInfo` is false, purchase a specific value in the `offerRates` `denominations` field.


# Go from zero to your first API call Source: https://docs.fluz.app/get-started One GraphQL endpoint. Create your account, generate credentials, exchange them for a scoped access token, and make your first purchase in staging. ## Setup steps Create your Fluz account and register your first application. [Prepare accounts →](/get-started/prepare-accounts) Get credentials and generate a scoped user access token. [API credentials →](/get-started/api-credentials) Pick an end-to-end happy path below and run it in the sandbox — every quickstart goes from a scoped token to a finished, verifiable result. [Choose a quickstart ↓](#quickstarts) ## Quickstarts Each quickstart is the complete flow for one job — copy‑paste runnable, staged so every step verifies the last, and done in minutes. Deposit funds, browse the merchant catalog, buy a gift card, and reveal its redemption details. Pick a card program, issue a card with the right spend controls, reveal it, and track its activity. Deposit, transfer between accounts, and withdraw — one ledger, verified at every step. Register a user, run KYC, and connect their account so you can transact on their behalf. Staging is stocked with test merchants, bank cards, bank accounts, KYC identities, and addresses — no real money, no real PII. # API credentials and access tokens Source: https://docs.fluz.app/get-started/api-credentials Exchange your API key for a short-lived, scoped user access token. ## Obtain your API credentials Once your application is created in the Developer Console, select it to find its **API Key**, **User ID**, and **Account ID**. **Where to find them:** the [Developers page](https://uni.staging.fluzapp.com/developers), inside the application you created. These credentials power API key authorization — primarily administrative calls and generating user access tokens. ![Obtain API credentials](https://files.readme.io/d1b78cd2ecfef1f945a9685f81efe0a8b30b7324d32d5ee62e610bced35585b4-obtain_api_creds.gif) Every Fluz API request uses a **user access token** in the `Authorization` header. You mint the token by calling the `generateUserAccessToken` mutation with your **API Key** in the `Authorization: Basic ` header, passing the **User ID** and **Account ID** from the console as arguments. ## Generate an access token ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Basic " \ -H "Content-Type: application/json" \ -d '{ "query": "mutation ($userId: UUID, $accountId: UUID, $scopes: [ScopeType!]!) { generateUserAccessToken(userId: $userId, accountId: $accountId, scopes: $scopes) { token refreshToken scopes } }", "variables": { "userId": "", "accountId": "", "scopes": ["LIST_OFFERS", "PURCHASE_GIFTCARD", "REVEAL_GIFTCARD"] } }' ``` The response contains the access token, a refresh token, and the scopes the token carries: ```json theme={null} { "data": { "generateUserAccessToken": { "token": "eyJhbGciOi...", "refreshToken": "eyJhbGciOi...", "scopes": ["LIST_OFFERS", "PURCHASE_GIFTCARD", "REVEAL_GIFTCARD"] } } } ``` Select `refreshToken` explicitly. If you request only `token` and `scopes`, you never receive one, and your only option when the access token expires is to mint a new one from scratch. See [Refresh an expired access token](/get-started/refresh-expired-access-token). ### Identifying the user `scopes` is the only always-required argument. Identify the user in one of two ways: For applications operating on **your own account**. Both values are shown on your application in the Developer Console. For **OAuth applications** operating on a customer's account — your own identifier for that user, the same value passed as `external_id` during the OAuth authorization flow. When you provide it, `userId` and `accountId` are optional. See [Client-facing OAuth grant flow](/client-facing-o-auth-grant-flow). Optional. Selects which seat transacts. Defaults to the most recently created seat. ## Use the token Attach the token to every GraphQL request against the transactional graph: ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"query":"query { getMerchants(name: \"Burger King\") { name slug } }"}' ``` Never ship your API key to a browser or mobile client. Mint access tokens server-side and forward only the token to the client if you must. ## If the token request returns 401 A rejected API key returns the same message whether it is unknown, malformed, or from the wrong environment: ```json theme={null} { "error": "Error verifying API basic token" } ``` The one exception is a real key on an application that has been disabled — that returns a `403` naming the status instead: `Application is not in a valid status: .` Work through these in order. This is the most common cause. Staging and live have **separate applications and separate credentials** — a key created in one is unknown to the other, and the error looks identical either way. Check where you created the application: | Console | Environment | Endpoint the key works against | | ------------------------------------ | ----------- | ----------------------------------------- | | `uni.staging.fluzapp.com/developers` | Staging | `transactional-graph.staging.fluzapp.com` | | `fluz.app/for-developers` | Live | `transactional-graph.fluzapp.com` | A live key sent to the staging endpoint fails here, and the reverse is also true. If you only have a live application, register a second one in staging. Your API key is a base64-encoded `app_id:app_secret` pair. Copy it whole from the console rather than reassembling it, and do not base64-encode it again — it is already encoded. Send it verbatim: ``` Authorization: Basic ``` If the key was regenerated in the console, older copies stop working immediately. Applications only authenticate while active. A deleted application is indistinguishable from a bad key — the same `401` comes back. A **disabled** application is the one case that looks different: the response is a `403` with `Application is not in a valid status: .` Confirm the application still exists in the console for the environment you are calling and has not been disabled. `userId` and `accountId` must be the values shown on the same application as the API key. Mixing IDs from one application with the key from another fails. Confirm the endpoint host matches the console you created the credentials in. Environment mismatch accounts for most first-run `401`s. ## Mint a new token before expiry Access tokens are short-lived. Mint a new one before the current one expires — don't wait for a `401`. See [Refresh an expired access token](/get-started/refresh-expired-access-token) for the exact call, and [Authentication](/concepts/authentication) for the full flow, including OAuth grants for customer-scoped tokens. ## Next steps Run the full happy path end to end — deposit funds, buy a gift card, and reveal it in the sandbox. Mint a fresh access token when the current one expires. # Make your first API call Source: https://docs.fluz.app/get-started/first-api-call Run an authenticated GraphQL request against staging and confirm your token works. With an access token in hand, you're ready to hit the API. Every call goes to a single GraphQL endpoint — the token decides whose account you're operating on. ## The endpoint | Environment | Endpoint | | ----------- | ---------------------------------------------------------------- | | Staging | `https://transactional-graph.staging.fluzapp.com/api/v1/graphql` | | Live | `https://transactional-graph.fluzapp.com/api/v1/graphql` | There are no versioned REST paths and no per-capability base URLs. You send every query and mutation to the same address. ## Confirm your token works `getMerchants` is the cheapest way to prove your setup end to end. It reads nothing sensitive, moves no money, and needs only the `LIST_OFFERS` scope. ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"query":"query { getMerchants(name: \"Burger King\") { name slug } }"}' ``` A working token returns the merchant: ```json theme={null} { "data": { "getMerchants": [ { "name": "Burger King", "slug": "burger-king" } ] } } ``` That single response confirms four things at once: your endpoint is right, your token is valid, it carries the scope the query needs, and it is scoped to an account that can read the catalog. **An empty array is not an error.** `getMerchants` filters by name, so a merchant that isn't in the staging catalog returns `[]` with no `errors` block. If you get an empty result, try the query without a `name` argument to see what staging currently carries. ## When it doesn't work The token was rejected before the query ran. Most often the credentials came from the wrong environment — staging and live have separate applications. See [If the token request returns 401](/get-started/api-credentials#if-the-token-request-returns-401). The token is valid but lacks the scope the operation needs. The message names it, for example `getMerchants requires LIST_OFFERS`. Mint a new token with that scope added — scopes are fixed at mint time and cannot be widened afterwards. The query doesn't match the schema — usually a field that doesn't exist or a wrong argument shape. The response names the offending field. Check the operation in the [API reference](/api-reference/overview). ## Where to next Deposit funds, buy a gift card, and reveal its redemption details — the full happy path. Pick a card program, issue a card with spend controls, and track its activity. Money-moving mutations take an `idempotencyKey`. Read this before your first write. Every query, mutation, and type. # Introduction to the Fluz API Source: https://docs.fluz.app/get-started/introduction-to-the-fluz-api The Fluz API lets you embed money movement directly into your platform, program, or workflow — pay-ins, wallets, spend, rewards, and disbursements — through a single integration with one unified ledger. Fluz has processed over \$5 billion across cards, wallets, pay-ins, payouts, and bill payments. This overview covers what you can build, how the API works at a high level, and the fastest path to your first successful call. Skim it before diving into the guides. 📘 **Using AI tools?** Point your assistant at our machine-readable docs index — [llms.txt](/llms.txt) — a complete, always-current map of every guide, recipe, and changelog entry. You can also connect via MCP at [/mcp](/mcp). ## Five ways money moves The API maps to Fluz's core money-movement solutions, which you can use together or on their own: * **Pay-Ins** — Bring funds into your platform through configurable deposit and funding flows, with support for multiple payment methods (bank, card, PayPal, wallet balance), automated settlement and reconciliation, and real-time balance updates. * **Wallets** — Open dedicated spend accounts that govern how funds are held and used, backed by user onboarding and identity verification, virtual account numbers, and a universal ledger. * **Spend** — Issue network-accepted, branded virtual cards embedded in your product, with user-controlled balances, merchant and category spend controls, and engagement and loyalty features. * **Rewards** — Distribute incentives through digital gift cards and prepaid cards with instant delivery, flexible funding sources, and real-time tracking. * **Disbursement** — Deliver fast, secure payouts across preferred payment methods, with open-loop payout options, embedded payout flows, and built-in compliance. ## What you can build A few flagship patterns the API is designed for: **Embedded wallets** — Move money in both directions between your platform and your customers. A one-time OAuth connection creates a persistent wallet-to-wallet link, so you can pay out with a single API call, let customers spend via virtual card, bank transfer, bill pay, or gift cards, track every movement in a real-time ledger with merchant-level data, and accept pay-ins back in — all through one integration. See the [embedded wallets solution](https://fluz.app/us/embedded-wallets/). **Open-loop prepaid cards** — Issue configurable Visa and Mastercard cards for incentives, recognition, and disbursements. Cards can be virtual or physical, single-load or reloadable, Apple/Google Wallet–ready, restricted by category or merchant, and ordered in bulk (up to 10,000 at a time) with white-label or co-branded card art. See [open loop cards](https://fluz.app/us/open-loop/). These power real-world programs like employee recognition, sales SPIFFs, customer loyalty, referral rewards, gig-worker and creator payouts, insurance claim payouts, market-research incentives, and user spend/allowance programs. Beyond these, the API also covers gift card purchasing (a catalog of thousands of brands with offer comparison, best-offer lookup, and stock checks), authorized users, transaction reporting and annotation, embedded JavaScript widgets with webhooks, and customer registration with KYC/KYB verification. ## How the API works A few things to know before your first request: * **It's a GraphQL API.** You send queries and mutations to a single endpoint rather than calling REST routes. The staging endpoint is `https://transactional-graph.staging.fluzapp.com/api/v1/graphql`. * **Authentication is two-step.** You authenticate with your API key (Basic auth) to generate a short-lived **User Access Token**, which carries the specific permission **scopes** an operation requires (for example, `CREATE_VIRTUALCARD`). You pass that token as a Bearer token on subsequent requests. See the [Authentication & Authorization Guide](/concepts/authentication). * **Test before you go live.** A full [staging environment](/concepts/environments) lets you build against sandbox keys, test bank accounts, and test card offers before switching to production. * **Requests are idempotent.** Use [idempotency keys](/concepts/idempotency) to safely retry requests without duplicating purchases or transfers. * **Multiple paths to launch.** Beyond the raw API, you can use low/no-code widgets and plug-and-play snippets, drop-in API adapters for existing integrations, and an OAuth-secured KYB/KYC "Comply" widget to automate onboarding. ## Built on regulated, secure infrastructure Fluz applies bank-grade controls across every money movement: **SOC 2 Type II** and **PCI DSS** compliant, continuous 24/7 fraud monitoring, and built-in KYC/KYB. Spend account funds are held by FDIC-insured banking partners, with pass-through deposit insurance coverage available up to \$10,000,000 under certain conditions.\* ## Get started 1. [**Prepare your accounts**](/get-started/prepare-accounts) — create your Fluz account and register your first application. 2. [**Obtain your API credentials**](/get-started/api-credentials) and [**generate a User Access Token**](/recipes/generate-user-access-token). 3. [**Perform your first API actions**](/get-started/first-api-call) — deposit funds, browse merchants, and purchase your first gift card. From there, explore the guides for [virtual cards](/features/virtual-cards), [wallets & transfers](/features/create-spend-accounts), and [embedded widgets](/developers/widgets), or jump to the [API Reference](/api-reference/overview). *** **Want to learn more?** Contact us at [humans@fluz.app](mailto:humans@fluz.app) to speak with our experts or request a demo. *** > \**FDIC disclosure: Fluz is a financial technology company, not an FDIC-insured bank. Banking services are provided by our FDIC-insured bank partners. FDIC insurance only covers the failure of an FDIC-insured bank. FDIC insurance up to \$10,000,000.00 is available on customer funds through pass-through insurance provided by bank partners where we have a direct relationship for the placement of deposits and into which customer funds are deposited, but only if certain conditions have been met. There may be a risk that FDIC insurance is not available because conditions have not been satisfied. In such cases, funds may not be fully insured in the event the insured depository institution where the funds have been deposited were to fail.* Get started [here](/get-started/prepare-accounts). # Prepare Your Accounts Source: https://docs.fluz.app/get-started/prepare-accounts Before writing any code, you need the right accounts and keys. This is the first step in getting up and running with the Fluz API. Before you can authenticate or make your first call, you need three things: a Fluz account, access to the **Developers** area, and a registered application that issues your API credentials. Everything below takes just a few minutes. **Start in the sandbox.** All setup happens in Fluz's sandbox (staging) environment first, at [uni.staging.fluzapp.com](https://uni.staging.fluzapp.com). Sandbox lets you build and test with sandbox keys, [test bank accounts](/test-bank-accounts), and [test card offers](/test-bank-cards) — no real money moves. When you're ready to go live, see [Staging vs. Live Environment](/concepts/environments). * **New user?** Visit the [Fluz sandbox](https://uni.staging.fluzapp.com) and sign up. * **Existing user?** Log in and open the **Developers** section — or go straight to [uni.staging.fluzapp.com/developers](https://uni.staging.fluzapp.com/developers). The **Developers** section is where your applications and API credentials live. Registering an application is what generates the API key and credentials you'll authenticate with. From the **Developers** section: 1. Click **Create new Application**. 2. Fill in **App name and logo**, **Subtitle**, and **What is the main purpose of this app?** 3. Click **Create new app**. ![Creating a new application in the Developers section](https://files.readme.io/5dfac33294fa2cf54478a69ecd5a8d0cfdf0c45735b869afca1ee22e4c496135-create_new_app.gif) **When you're done, you'll have:** a Fluz sandbox account with access to the **Developers** area, a registered application, and everything you need to retrieve your API credentials. ## Next steps With your account and application in place, you're ready to authenticate. The full flow is documented in the [Authentication & Authorization guide](/concepts/authentication). Retrieve the API key tied to your application and exchange it for a scoped access token. A runnable recipe that mints a token with the scopes your integration needs. *** **Want to learn more?** Contact us at [support@fluz.app](mailto:support@fluz.app) to speak with our experts or request a demo. # Refresh an expired access token Source: https://docs.fluz.app/get-started/refresh-expired-access-token Exchange a refresh token for a fresh user access token without re-minting from scratch. User access tokens are short-lived JWTs. When one expires, exchange the refresh token you received alongside it for a new access token. **Two different flows.** This page covers **API-key applications** operating on your own account. If you are an **OAuth platform application** acting on a customer's account, refresh customer tokens through the OAuth token refresh endpoint instead — see [Refresh an OAuth access token](/refresh-o-auth-access-token). ## How the two tokens relate ```mermaid theme={null} flowchart TD KEY[API key
Basic auth] KEY -->|generateUserAccessToken| PAIR subgraph PAIR[One mint returns both] AT[access token
short-lived] RT[refresh token
long-lived] end AT -->|Bearer, every request| API[Fluz API] AT -.->|expires| EXP[401] RT -->|refreshUserAccessToken
Basic auth| NEW[new access token only] NEW -->|Bearer| API RT -.->|reused, not replaced| RT style KEY fill:#e8e8e8,stroke:#888 style AT fill:#d4edda,stroke:#5a9 style NEW fill:#d4edda,stroke:#5a9 style RT fill:#fff3cd,stroke:#c93 ``` Two things catch people out. The refresh call authorizes with your **API key**, not with the expired access token. And it returns **only** a new access token — the refresh token you already hold stays valid and is not replaced. ## Where the refresh token comes from `generateUserAccessToken` returns a refresh token alongside the access token. Request it explicitly — if you only select `token` and `scopes`, you never receive one: ```graphql theme={null} mutation ($userId: UUID, $accountId: UUID, $scopes: [ScopeType!]!) { generateUserAccessToken(userId: $userId, accountId: $accountId, scopes: $scopes) { token refreshToken scopes } } ``` Store the `refreshToken` server-side with the access token. See [API credentials](/get-started/api-credentials) for the full minting call. ## Refresh the token `refreshUserAccessToken` authorizes with your **API key**, not with the expired access token. Replace `` and `` with your values: ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Basic " \ -H "Content-Type: application/json" \ -d '{ "query": "mutation ($refreshToken: String!) { refreshUserAccessToken(refreshToken: $refreshToken) { token scopes } }", "variables": { "refreshToken": "" } }' ``` The response carries a fresh access token and the scopes it grants: ```json theme={null} { "data": { "refreshUserAccessToken": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "scopes": ["LIST_OFFERS", "PURCHASE_GIFTCARD"] } } } ``` Use the new `token` in the `Authorization: Bearer ` header, exactly as before. **The refresh response does not include a new refresh token.** `refreshUserAccessToken` returns only `token` and `scopes`. Keep the refresh token you received from `generateUserAccessToken`. ## When to refresh Refresh proactively — at the start of a job or session — rather than waiting for a request to fail with a `401` and refreshing reactively. That keeps requests from failing under load. If you no longer hold a valid refresh token, mint a new access token from scratch with [`generateUserAccessToken`](/get-started/api-credentials). ## Next steps Mint your first access token and see the full set of arguments. The complete authentication model, including OAuth grants for customer-scoped tokens. # Gift Card Error Codes Source: https://docs.fluz.app/gift-card-error-codes | Error | Code | Message | | :--------------------------- | :------ | :------------------------------------------------------------------------------------------------------- | | INVALID\_STATUS\_OR\_OWNER | GC-0001 | Sorry, we are unable to update restricted purchases. | | INVALID\_PURCHASE\_AMOUNT | GC-0002 | Value or Fluzpay Amount should be a positive number! | | UNABLE\_TO\_RETRIEVE | GC-0003 | Unable to retrieve gift card record, please try again or call customer support! | | UNABLE\_TO\_PURCHASE | GC-0004 | Please try another payment method. If you continue experiencing issues, please contact our support team. | | UNABLE\_TO\_UPDATE\_STATUS | GC-0005 | Sorry, we are unable to update status, please try again or contact customer support. | | UNABLE\_TO\_REVEAL | GC-0006 | Unable to reveal gift card, please try again or contact customer support. | | UNABLE\_TO\_GENERATE\_EXPORT | GC-0007 | Unable to generate gift card export, please try again or contact customer support. |
# Gift Cards Overview Source: https://docs.fluz.app/gift-card-overview Read the merchant catalog, understand fixed versus variable offers, buy a card, and reveal it — the whole gift card arc and which page to read for each step. Buying a gift card through Fluz is four steps, and each one has a page of its own. This is the map. Pull the catalog, or ask for the best rate on one merchant. → [Read the catalog](#reading-the-catalog) Fixed or variable, stocked or generated on demand. → [Fixed vs. variable](#fixed-vs-variable-offers) One mutation, one card, one idempotency key. → [Buying](#buying-a-card) Get the code, PIN, or URL, and render it correctly. → [Revealing](#revealing-the-card) **Rates change constantly.** Fluz continuously re-prices to give customers the best available offer, and your rates are customized to your account. Never cache a rate and purchase against it later — re-confirm immediately before you buy. *** ## Vocabulary Five terms do most of the work in this section, and three of them sound alike. | Term | What it is | | :------------------ | :------------------------------------------------------------------------------------------------------------------------ | | **Merchant** | The brand — Starbucks, Best Buy. Has a `merchantId` and a human-readable `slug`. | | **Offer** | A specific purchasable deal from that merchant, with its own `offerId`. One merchant can have several. | | **Offer rate** | The reward attached to an offer — cashback, boosts, eligible denominations, permitted payment methods. | | **Denomination** | The face value of the card. Either chosen from a preset list or any amount in a range. | | **Delivery format** | How the card arrives: `URL`, `CODES`, `PIN_AS_CODE`, `PIN_WITH_URL`, or `CODE_WITH_PREFIX`. Determines how you render it. | Two identifiers are easy to confuse: `merchantId` identifies the brand, `offeringMerchantId` identifies the party offering that particular deal. You buy against an `offerId` or a `slug`, never against a `merchantId`. *** ## Reading the catalog Three ways in, for three different jobs. | Approach | Query | Use when | | :----------------------------- | :--------------------------------------- | :------------------------------------------------------------------------------------------ | | **Full catalog** | `getMerchants` | You're building a browsable storefront, syncing a local catalog, or comparing across brands | | **Best rate for one merchant** | `getOfferQuote` | You already know the brand and amount and just want today's best rate | | **CSV export** | Dashboard → **Stores** → generate export | Analysis, finance, or a human wants a spreadsheet | Filter `getMerchants` to gift cards with `offerTypes: { giftCardOffer: true, cardLinkedOffer: false }`. Card-linked offers exist in the catalog but only gift card and exclusive offers are purchasable through the API today. **The full catalog is a cached file, refreshed twice daily.** `getOfferQuote` is live. If the exact rate matters at purchase time — and it usually does — quote before you buy rather than trusting a catalog pull from this morning. Promotional rates are reflected in both, including in CSV exports generated during a promotion. Both queries are rate-limited, so paginate the full catalog rather than requesting it in one call. → [Get Catalog](/get-catalog) · [Get Gift Card Offers](/get-gift-card-offers) · [Get the Best Offer](/get-best-offer) ### Exclusive offers Rates are customized per account. If yours has negotiated rates, they appear as offers with `type: "EXCLUSIVE_RATE_OFFER"` carrying an `exclusiveRateId`. Pass that ID to `purchaseGiftCard` to force the purchase onto that rate; omit it and Fluz picks the best available. *** ## Fixed vs. variable offers This is the distinction that most shapes how you build, and a merchant can have both. The card comes in **preset denominations** — $25, $50, \$100 — and you buy one of them exactly. Frequently backed by **real inventory** Fluz holds, which is why fixed offers usually carry **better rates and higher purchasing limits**. Inventory is finite. It runs out. You choose **any amount within a min/max range**, and the card is generated in real time. No inventory to deplete — effectively unlimited supply. Usually a **lower reward rate** than the same brand's fixed offer. The practical trade-off: fixed offers pay better but can be exhausted mid-run; variable offers always work but pay less. High-volume ordering usually means taking fixed inventory first and falling back to variable. ### Where to read the purchasable amounts Two fields decide this, and the combination determines which field holds the answer. Get this wrong and you'll submit amounts the offer can't fulfill. | `denominationsType` | `hasStockInfo` | Purchasable amounts live in | Meaning | | :------------------ | :------------- | :------------------------------------ | :------------------------------------------------------- | | `FIXED` | `true` | `stockInfo` → `StockInfoFixedType` | Specific denominations with a countable `availableStock` | | `FIXED` | `false` | `offerRates.denominations` | Preset denominations, no inventory constraint published | | `VARIABLE` | `true` | `stockInfo` → `StockInfoVariableType` | A `minDenomination`–`maxDenomination` range | | `VARIABLE` | `false` | `offerRates.denominations` | Any amount in the published range | `stockInfo` is a **union type**. You must query it with inline fragments for *both* shapes, or you'll get nothing back for one of them: ```graphql theme={null} stockInfo { ... on StockInfoFixedType { __typename denomination availableStock } ... on StockInfoVariableType { __typename description minDenomination maxDenomination } } ``` Always include both fragments, even when you think you know which one you'll get. See [How the GraphQL API works](/concepts/graphql). Note that "has stock info" doesn't mean "has countable stock." On a variable offer, `stockInfo` returns a *range*, not a quantity. Only `StockInfoFixedType` carries an `availableStock` number you can decrement against. Populating `stockInfo` requires Fluz to confirm inventory with the vendor, and vendor response times vary — so requesting it makes the query slower. Only ask for it when you're about to act on it. → [Get Inventory on Stocked Offers](/get-inventory) *** ## Buying a card One mutation: `purchaseGiftCard`. Three decisions. ### 1. How to pick the offer | You pass | Behavior | | :------------------------------- | :---------------------------------------------------------------------------------------------- | | `offerId` | **Pinned.** Buys that exact offer. If it's depleted, the call fails — no fallback. | | `merchantSlug` | **Auto-select.** Buys the best available rate for that brand, falling back as inventory shifts. | | `merchantSlug` + `minRewardRate` | Auto-select **with a floor.** Fails rather than buying below your minimum rate. | | `exclusiveRateId` | Forces a specific negotiated rate. | Pinning gives you certainty about the rate; auto-select gives you certainty about fulfillment. `merchantSlug` + `minRewardRate` is the middle ground and usually the right default for automated ordering. ### 2. How to pay At least one funding source is required, and you can combine your Fluz balance with another. | Field | What it does | | :----------------------------------------------- | :--------------------------------------------------------------- | | `balanceAmount` | **How much** to pay from your Fluz balance | | `userCashBalanceId` | **Which spend account** that balance draws from | | `bankCardId` / `bankAccountId` / `paypalVaultId` | External funding sources | | `defaultToBalance` | Fall back to balance if another method fails. Defaults to `true` | **If your account holds more than one spend account, always pass `userCashBalanceId` explicitly.** Omit it and Fluz draws from whichever account is flagged `isDefault` — which can change without your code changing, silently redirecting where your money comes from. This is the most common cause of surprise insufficient-funds failures. In automated pipelines, also set `defaultToBalance: false` so a purchase either draws from the account you named or fails cleanly. ### 3. Idempotency `idempotencyKey` is required, and it's the difference between a retry and a double purchase. One key per intended card, reused on every retry of that same card. → [Idempotency](/docs/idempotency-requests) → [Purchase Gift Card](/purchase-gift-card) ### Buying more than one **One call buys exactly one card**, on one offer, at one rate. There's no quantity field and no blending across offers. For ten cards, send ten calls with ten distinct idempotency keys. What happens when you outrun inventory depends on how you picked the offer: * **Pinned (`offerId`)** — once the stocked offer is depleted, remaining calls fail. No automatic fallback. * **Auto-select (`merchantSlug`)** — remaining calls move to the next-best offer, often a variable one at a lower rate, unless `minRewardRate` blocks it. → [Purchase in Bulk](/bulk-gift-card-purchasing) ### Ordering at volume Purchases against the same Fluz account process **sequentially**. Fire a large batch at once and calls queue behind each other, occasionally taking minutes to return. **A client timeout is not a cancellation.** Fluz keeps processing a request you've stopped waiting for. Treat a timeout as an *unknown* outcome, never a failure. Resolve it by retrying with the **same** `idempotencyKey` — the retry returns the original purchase if it already succeeded, and won't double-charge. Issuing a fresh key for a purchase you already attempted is exactly how duplicate orders happen. Set client timeouts to about a minute, pace requests in waves rather than all at once, and spread heavy volume across multiple accounts. *** ## Revealing the card A purchase gives you a `giftCardId`. Redemption details come from a second call. Skip this if you just purchased and already hold the `giftCardId`. Otherwise `getGiftCards` lists them with `purchaseId`, `purchaseDisplayId`, `purchaseValue`, `currentValue`, and `status` — enough to reconcile orders without revealing every card. `revealGiftCardByGiftCardId` returns `code`, `pin`, `url`, and `termsAndConditions`. Three things that catch people out: * **Not every card has all three fields.** Some merchants issue a code with no PIN; some issue only a URL. Fluz passes through whatever the merchant provides — handle nulls. * **Render according to `deliveryFormat` and `barcodeType`,** and take `deliveryFormat` from `getGiftCards`, not from the merchant's current offer. Offers change; the card was issued under the format in force at purchase time. `barcodeType` is `NONE`, `C128`, `PDF417`, or `QRCODE`; when it's `NONE`, consider displaying the `faceplateUrl` instead. * **Details may not be ready instantly.** Poll with exponential backoff — 300ms, doubling, capped at three minutes — and stop as soon as details return. → [View Gift Cards](/view-gift-card) *** ## Scopes | Scope | Needed for | | :------------------ | :----------------------------------- | | `LIST_OFFERS` | `getMerchants`, `getOfferQuote` | | `PURCHASE_GIFTCARD` | `purchaseGiftCard` | | `REVEAL_GIFTCARD` | `revealGiftCardByGiftCardId` | | `LIST_PURCHASES` | `getUserPurchases`, purchase history | | `LIST_PAYMENT` | `getUserCashBalances`, `getWallet` | Enable these on your app's **Permissions** tab before you build. A scope you request but haven't enabled is silently dropped. → [Configure OAuth App](/configure-o-auth-app) *** ## When things fail | Code | Meaning | | :-------- | :------------------------------------------------ | | `GC-0002` | Purchase amount or Fluz Pay amount isn't positive | | `GC-0003` | Couldn't retrieve the gift card record | | `GC-0004` | Purchase failed — try another payment method | | `GC-0006` | Couldn't reveal the card | Full list: [Gift Card Error Codes](/gift-card-error-codes). Before refunding an end user on a failed or timed-out purchase, **retry with the same `idempotencyKey` or look up the purchase by ID.** Timed-out requests frequently succeeded, and the code stays revealable until the purchase is refunded. *** ## Next steps Pull merchants and their offers. Live quote for one merchant and amount. Stock on fixed, stocked offers. The mutation, in full. Ordering many cards, and depletion behavior. Reveal codes, PINs, and URLs. # Merchant Catalog Overview Source: https://docs.fluz.app/merchant-catalog In order to make gift card purchases with the Fluz API, you'll need to determine what merchant and offer you would like to use. Fluz offers two ways to access our merchant catalog, tailored to different use cases: 1. **Full Merchant List with Offers:** Retrieve a comprehensive list of all available merchants and their offers. 2. **Best Rate Query:** Easily determine the best available rate for a specific merchant. Before you begin using the catalog, there are a few important details to understand. > 🚧 Cashback rates are subject to change. > > We do our best to always give our customers the best offers available. This means that our rates change regularly. Always confirm the rate before making a purchase. ## Rate Settings on Catalog Requests Rates in the catalog are customized to your account. If your account has any exclusive offers, these will be reflected in the catalog within the `type` field of the `offer` object. ## Catalog Update Frequency The full catalog file is updated twice daily. When you request the full catalog via the Fluz API, you'll receive a cached version of the most recent update. ## Offer Types Merchants can feature, gift card offers, card-linked offers and exclusive offers. However, currently, only gift card and exclusive offers are currently accessible via the API. ## Pulling Catalog From Your Dashboard Besides accessing the catalog via API, you can also download it directly from your Fluz dashboard: 1. Log in to your Fluz dashboard and navigate to the "Stores" view. 2. There, you’ll find an option to generate a catalog export. 3. The export will be downloaded as a CSV file. Keep in mind that the same update frequency mentioned earlier applies to these CSV files. > 📘 Promotional rates are reflected in the catalog export as well. If you run any of these exports while the Fluz team is running a promotion, those updated rates will be reflected on your export. # Fluz API documentation Source: https://docs.fluz.app/overview Embed money movement into your product — pay-ins, wallets, virtual cards, gift cards, and payouts — through one GraphQL API and a single unified ledger. ## Where do you want to start? Use the API to move money in and out of your own Fluz account. Credentials, auth, and your first call in staging — about five minutes. Register or connect customer accounts with OAuth, then run the exact same APIs on their behalf. ## Explore the docs Credentials, auth, and your first call in staging. Cards, gift cards, wallets, and transactions — on your account or any connected account. Connect customer accounts and run the same capabilities on their behalf. Widgets, connectors, webhooks, and reference material. ## Popular quickstarts Deposit funds, browse the catalog, buy a gift card, and reveal its redemption details. Pick a card program, issue a card with spend controls, reveal it, and track activity. Register a user, run KYC, and connect their account so you can transact on their behalf. Create an app, select your funding, and send a virtual card to one or many recipients. Every end-to-end flow, staged so each step verifies the last. ## Browse by capability * [Create a card](/features/create-card) * [Edit a card](/features/edit-virtual-card) * [Reveal card details](/recipes/reveal-virtual-card) * [Track transactions](/features/get-virtual-card-transactions) * [Issue in bulk](/features/create-bulk-order) * [Browse the catalog](/get-catalog) * [Get the best offer](/get-best-offer) * [Purchase a gift card](/purchase-gift-card) * [Purchase in bulk](/bulk-gift-card-purchasing) * [View gift cards](/view-gift-card) * [Link a bank account](/features/link-via-plaid) * [Deposit funds](/features/deposit-from-external-accounts) * [Transfer between accounts](/features/transfer-between-spend-accounts) * [Withdraw](/features/withdraw-to-external-account) * [Register users](/user-registration) * [Run KYC verification](/user-kyc-verification) * [Connect accounts with OAuth](/create-an-o-auth-app) * [Manage authorized users](/features/create-authorized-users) * [Embedded widgets](/developers/widgets) * [API connectors](/API-connectors-overview) * [Webhooks](/fluz-dashboard/webhooks) * [How GraphQL works](/concepts/graphql) * [Authentication](/concepts/authentication) * [Idempotency](/concepts/idempotency) * [Rate limits](/concepts/rate-limits) ## Stay in sync Recent updates, additions, and breaking changes to the Fluz API. SOC 2 Type II, PCI DSS, 24/7 fraud monitoring, and FDIC-insured banking partners. **Using AI tools?** Point your assistant at `llms.txt` for a machine-readable index, or connect via MCP at `docs.fluz.app/mcp`. # Purchase Gift Card Source: https://docs.fluz.app/purchase-gift-card Purchase a gift card with the purchaseGiftCard mutation, including how to select the spend account funds are drawn from. Once you've determined your preferred offer, use the `purchaseGiftCard` mutation to purchase your gift card. This mutation requires a `PurchaseGiftCardInput` input object. ## Sample Mutation Here's the quickest way to start purchasing a gift card. You are able to customize your query from the `UserPurchase` object. See the [API reference](/api-reference/overview). ```graphql theme={null} mutation purchaseGiftCard($input: PurchaseGiftCardInput!) { purchaseGiftCard(input: $input) { purchaseDisplayId purchaseAmount giftCard { giftCardId status termsAndConditions } } } ``` ## Fields ### Required `idempotencyKey` — A unique client-generated UUID to ensure a request is processed only once. `offerId `**or** `merchantSlug` — Use the `offerId` or `merchantSlug` from the [`getOfferQuote`](/get-best-offer) query to get the best offer. Using the `merchantSlug` will automatically purchase the best offer rate for that merchant. `amount` — The gift card amount you'd like to purchase. *** ### Rate selection `exclusiveRateId` — The unique identifier for a specific exclusive rate offer. When provided, this forces the purchase to use the specified exclusive rate. If not provided, the system will automatically select the best available rate. The `exclusiveRateId` can be found in the [`getMerchants`](/get-catalog) query response for offers with type `EXCLUSIVE_RATE_OFFER` when you provide `exclusiveRateId` in your query request under `offers`. `minRewardRate` — If you want to specify the minimum reward rate to purchase if the `merchantSlug` option is chosen. *** ### Payment At least one funding source is required. You may also choose to combine your Fluz balance with another funding source. `balanceAmount` — If you want to pay for your gift card with your Fluz balance, define the amount of balance here. You can use the [`getWallet`](/check-account-balance) query to check your balances. `userCashBalanceId` — The **spend account** that `balanceAmount` is drawn from. Pass this explicitly whenever your account holds more than one spend account. If omitted, Fluz draws from the spend account flagged `isDefault: true`. This is a modifier on `balanceAmount`, not an alternative funding source — see [Choosing a spend account](#choosing-a-spend-account). `bankAccountId` — If you want to pay with an external linked bank account, define the bank account ID. This is not a spend account. `bankCardId` — If you want to pay with a bank card, define the bank card ID. `paypalVaultId` — If you want to pay with a PayPal account, define the PayPal account ID. `defaultToBalance` — If you want to use your Fluz balance as the fallback payment method in case your other payment methods fail, set `defaultToBalance` to `true`. By default, this is set to `true`. If you change this setting to `false`, the system will not attempt to use your Fluz balance as a backup payment method. *** ### Expense details `memo` — If you want to attach a note to this transaction, provide a free-text memo here. Max 255 characters. `transactionCategory` — If you want to categorize this transaction, provide a category name. Categories are created automatically on first use and reused if the same name is passed again. `attachmentId` — If you want to attach a file to this transaction, provide the ID returned by the upload endpoint. See [Add Expense Details](/features/add-expense-details). > #### See [Add Expense Details](/features/add-expense-details) for full details on uploading attachments and working with memos and categories. #### PurchaseGiftCardInput ```json theme={null} { "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "offerId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "amount": 987.65, "balanceAmount": 123.45, "userCashBalanceId": "85de1b3e-4e72-462c-8ed1-a6f4982e22f7", "bankAccountId": "0285c162-fb2f-4c32-b076-29166471f570", "bankCardId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "paypalVaultId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "exclusiveRateId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "merchantSlug": "xyz789", "minRewardRate": 5.6 } ``` ## Choosing a spend account A **spend account** is a cash balance account inside Fluz that holds the funds a purchase draws from. Your account can hold several of them — for example "Main account," "Operations," or "Client A" — each with its own nickname and its own balance. This section covers only what you need in order to fund a gift card purchase. For the full picture — how spend accounts work, the three balances each one tracks, and how to create, rename, and close them — see [Spend Accounts](/features/spend-accounts) in the Wallet documentation. **"Spend account," "cash balance," and `UserCashBalance` all refer to the same object.** The product surfaces it as a spend account. The API names the type `UserCashBalance`, so the field on this mutation is `userCashBalanceId` — not `accountId`. Note that `bankAccountId` is unrelated: it refers to an *external* linked bank account, not a spend account. ### Which field does what When you fund a purchase from your Fluz balance, two fields work together: | Field | Purpose | | :------------------ | :---------------------------------------------------------- | | `balanceAmount` | **How much** of the purchase to pay from your Fluz balance. | | `userCashBalanceId` | **Which spend account** that balance is drawn from. | These are not mutually exclusive. `userCashBalanceId` has no effect unless the purchase draws on balance — either through `balanceAmount` or through a `defaultToBalance` fallback. ### Default behavior when `userCashBalanceId` is omitted If you omit `userCashBalanceId`, Fluz draws from the spend account flagged `isDefault: true`. **If your account holds more than one spend account, always pass `userCashBalanceId` explicitly.** Relying on the default is the most common cause of unexpected insufficient-funds failures. A deposit routed to a newly created spend account, or a change to which account is flagged as default, will silently redirect where your purchases draw from — your requests are unchanged, but they now resolve to an account with a different balance. Passing the ID explicitly makes the funding source deterministic. To move funds between spend accounts — for example, to unblock an order drawing from the wrong account — see [Transfer Funds Between Spend Accounts](/features/transfer-between-spend-accounts). Internal transfers settle immediately. ### Step 1 — Retrieve your spend account IDs Use the `getUserCashBalances` query to list your spend accounts. This requires the `LIST_PAYMENT` scope. ```graphql theme={null} query GetUserCashBalances($filter: UserCashBalanceFilterInput) { getUserCashBalances(filter: $filter) { userCashBalances { userCashBalanceId nickname availableCashBalance isDefault status } totalCount } } ``` Variables: ```json theme={null} { "filter": { "status": ["ACTIVE"] } } ``` Sample response: ```json theme={null} { "data": { "getUserCashBalances": { "userCashBalances": [ { "userCashBalanceId": "6d1b4b19-deef-42f5-80d7-ec34804ce090", "nickname": "Main account", "availableCashBalance": "425108.80", "isDefault": true, "status": "ACTIVE" }, { "userCashBalanceId": "1c1b5fcc-eb21-44c5-b678-9c41f3fa21b4", "nickname": "Gift card orders", "availableCashBalance": "12500.00", "isDefault": false, "status": "ACTIVE" } ], "totalCount": 2 } } } ``` Store the `userCashBalanceId` of the account you intend to spend from. The ID is stable, so you can hold it in configuration rather than looking it up on every purchase — though you should check `availableCashBalance` before high-volume runs. See [Get Spend Accounts](/features/get-spend-accounts) for the full field reference, filter options, and pagination. ### Step 2 — Pass the spend account on the purchase ```graphql theme={null} mutation purchaseGiftCard($input: PurchaseGiftCardInput!) { purchaseGiftCard(input: $input) { purchaseDisplayId purchaseAmount fluzpayAmount giftCard { giftCardId status } } } ``` Variables — a \$100 card paid entirely from the "Gift card orders" spend account: ```json theme={null} { "input": { "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "merchantSlug": "burger-king", "amount": 100.00, "balanceAmount": 100.00, "userCashBalanceId": "1c1b5fcc-eb21-44c5-b678-9c41f3fa21b4", "defaultToBalance": false } } ``` Setting `defaultToBalance: false` prevents any implicit fallback, so the purchase either draws from the spend account you named or fails cleanly. In an automated ordering pipeline, this is usually the behavior you want. ### Splitting a purchase across balance and another funding source `userCashBalanceId` scopes only the balance portion of a purchase. To pay part from a spend account and the remainder from a linked bank card: ```json theme={null} { "input": { "idempotencyKey": "7b2e4c91-3d18-4a55-9e07-2c8f1a6b4d33", "merchantSlug": "burger-king", "amount": 100.00, "balanceAmount": 40.00, "userCashBalanceId": "1c1b5fcc-eb21-44c5-b678-9c41f3fa21b4", "bankCardId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19" } } ``` Fluz draws $40.00 from the named spend account and charges the remaining $60.00 to the bank card. ### Learn more about spend accounts Spend accounts are part of your Fluz wallet, and they are not limited to gift cards — virtual cards are funded from a spend account, and deposits land in one. * [**Spend Accounts**](/features/spend-accounts) — the full model: balances, the default account, the account lifecycle, and how to create, rename, and close accounts. * [Get Spend Accounts](/features/get-spend-accounts) — look up your accounts and their IDs. * [Transfer Funds Between Spend Accounts](/features/transfer-between-spend-accounts) — move balance between accounts instantly. * [Create Virtual Card](/features/create-virtual-card) — fund a virtual card from a spend account. * [Deposit Funds](/features/deposit-from-external-accounts) — add funds to a specific spend account. ## Sample Response Once your purchase is complete, you'll get a response that looks something like this: ```json theme={null} { "data": { "purchaseGiftCard": { "purchaseId": "255f8245-02c7-4817-901e-15fe265f6968", "purchaseDisplayId": "1019688", "purchaseBankCardId": "255f8245-02c7-4817-901e-15fe265f6968", "bankAccountId": "255f8245-02c7-4817-901e-15fe265f6968", "purchaseAmount": 123.45, "fluzpayAmount": 85.0, "seatRewardValue": 0.05, "paypalVaultId": "255f8245-02c7-4817-901e-15fe265f6968", "createdAt": "2007-12-03T10:15:30Z", "giftCard": { "giftCardId": "85de8b3e-4e72-462c-8ed1-a6f4982e22f7", "purchaserUserId": "85de8b3e-4e72-462c-8ed1-a6f4982e22f7", "endDate": "2007-12-03T10:15:30Z", "status": "ACTIVE", "termsAndConditions": "Except as required by law, Gift Cards cannot be transferred for...", "createdAt": "2007-12-03T10:15:30Z", "merchant": { "merchantId": "85de8b3e-4e72-462c-8ed1-a6f4982e22f7", "name": "Burger King", "slug": "burger-king", "logoUrl": "https://storage.googleapis.com/.../burger-king-logo.jpg", "faceplateUrl": "https://storage.googleapis.com/.../burger-king-faceplate.png", "offers": [ { "offeringMerchantId": "85de8b3e-4e72-462c-8ed1-a6f4982e22f7", "offerId": "85de8b3e-4e72-462c-8ed1-a6f4982e22f7", "type": "GIFT_CARD_OFFER", "deliveryFormat": "CODES", "barcodeType": "C128", "hasStockInfo": false, "offerRates": [ { "maxUserRewardValue": 5.0, "cashbackVoucherRewardValue": 1.0, "boostRewardValue": 0.5, "displayBoostReward": true, "denominations": [25, 50, 100], "allowedPaymentMethods": ["CREDIT_CARD", "PAYPAL"] } ], "denominationsType": "VARIABLE", "stockInfo": [] } ] } } } } } ``` ### **Cashback rates are subject to change.** We do our best to always give our customers the best offers available. This means that our rates change regularly. Always confirm the rate before making a purchase. ## Buying more than one card A single `purchaseGiftCard` call buys exactly one gift card, on one offer, at one rate. There is no quantity field, and a call is never split or blended across offers or rates. To buy several cards, send the mutation once per card, each with its own unique `idempotencyKey`. Because each card is its own call, ordering more cards than a stocked offer has in inventory resolves per call: * `offerId `**(pinned offer):** once the stocked offer is depleted, the remaining calls fail with `GC-0009`. There is no automatic fallback to another offer or rate. * `merchantSlug `**(auto-select):** the remaining calls auto-select the next-best available offer — often a variable offer at a lower reward rate — unless `minRewardRate` blocks the lower rate. For the full per-call breakdown, the `minRewardRate` rate-floor pattern, and the `GC-0009` response, see [Purchase in Bulk](/bulk-gift-card-purchasing). ### Ordering at volume: concurrency, timeouts, and retries Purchases that draw on the same Fluz account are processed sequentially. When many `purchaseGiftCard` calls are submitted at the same time against a single account, they queue behind one another, and individual calls can take longer to return — occasionally up to a few minutes under heavy load. Calls that aren't queued typically return within seconds. To keep latency predictable and avoid false failures when ordering at volume: * **Pace your concurrent requests.** Instead of firing an entire batch simultaneously against one account, submit in smaller waves, or spread volume across multiple accounts. This keeps per-call latency low. * **Use a generous client timeout.** Fluz does not abandon an in-flight purchase after a few seconds — a request can still be legitimately processing and will return a valid result. A short client-side timeout (for example, 30 seconds) may cause you to give up on a purchase that ultimately succeeds. Set your timeout high enough to absorb occasional multi-minute processing under load. We recommend 1 minute. * **A client timeout is not a cancellation.** Closing your connection does not cancel a request that Fluz has already accepted; it continues processing to completion. Treat a timeout as an *unknown* outcome, not a failure. * **Resolve timeouts by retrying with the same** `idempotencyKey`**.** Reissue the identical request with the identical `idempotencyKey`. Because the key guarantees the purchase is processed at most once, the retry returns the original purchase if it already succeeded — it will not create a duplicate or a second charge. Never issue a new `idempotencyKey` for a purchase you've already attempted; doing so is what produces duplicate orders. If a purchase timed out on your side and you're unsure of its outcome, **retry with the same** `idempotencyKey`**, or look up the purchase by its purchase ID, before refunding the end user.** A timed-out request has often already succeeded on Fluz's side, and the gift card code remains revealable until the purchase is refunded. ## Next Steps Now it's time to reveal your gift card details for use. Learn how to do so here: [View Gift Cards](/view-gift-card) # Issue 100 Cards at Once Source: https://docs.fluz.app/quickstart/bulk-issue-virtual-cards Create a bulk virtual card order, poll it to completion, and handle partial failures — the programmatic issuance pattern at scale. This Quickstart issues 100 virtual cards in one request. You'll place an asynchronous bulk order, poll its status as cards are created, and retrieve the full card details — the same pattern whether you're issuing 10 cards or 10,000. **Prerequisites** * A **Fluz account with a staging application** — Steps 1–2 of any quickstart, or see [Prepare your accounts](/get-started/prepare-accounts) and [API credentials](/get-started/api-credentials). * A token with **`CREATE_VIRTUALCARD`** to place the order and **`REVEAL_VIRTUALCARD`** to poll its status. Without the second, step 2 fails with `AUTH-0031`. * Enough available balance to cover the order — 100 cards × the spend limit you set. * A billing address on the account, or a `billingAddress` on each order item. Without one, card creation fails and the order completes with `failedCardCreations` equal to the order size. ## The full flow A bulk order is asynchronous. The mutation returns immediately with an `orderId` and a `PENDING` status; the cards are created in the background. **The order and the cards succeed independently** — an order can reach `COMPLETED` with some cards failed, so the order status alone never tells you whether you got what you asked for. ```mermaid theme={null} stateDiagram-v2 [*] --> PENDING: createVirtualCardBulkOrder PENDING --> IN_PROGRESS: cards being created IN_PROGRESS --> COMPLETED: all cards attempted IN_PROGRESS --> FAILED: order could not be processed COMPLETED --> AllSucceeded: successfulCardCreations == totalCards COMPLETED --> PartialFailure: failedCardCreations > 0 AllSucceeded --> [*] PartialFailure --> Reissue: place a new order for the shortfall Reissue --> [*] FAILED --> [*] ``` Always reconcile `successfulCardCreations` and `failedCardCreations` against `totalCards` before treating an order as done. `orderStatus` has one more possible value not shown above — `CANCELED` — which, like `FAILED`, means the order will produce no more cards. One `createVirtualCardBulkOrder` call creates all 100 cards. Each entry in `orderItems` is a card configuration with a `quantity` — here, 100 identical single-use disbursement cards. Unlike most money-moving mutations, this one takes **no `idempotencyKey`** — the input accepts only `offerId` and `orderItems`. See [Idempotency](/concepts/idempotency) for where the key does apply. ```graphql Mutation theme={null} mutation createVirtualCardBulkOrder($input: CreateVirtualCardBulkOrderInput!) { createVirtualCardBulkOrder(input: $input) { orderId orderStatus } } ``` ```json Variables theme={null} { "input": { "offerId": "592c394e-26cc-44ac-a145-a5f81301fe77", "orderItems": [ { "quantity": 100, "spendLimit": 25.00, "lockCardNextUse": true, "cardNickname": "July payout batch", "primaryFundingSource": "FLUZ_BALANCE" } ] } } ``` ```json Response theme={null} { "data": { "createVirtualCardBulkOrder": { "orderId": "ZTBhYTg2YmQt...", "orderStatus": "PENDING" } } } ``` The `offerId` applies to **every** card in the order; mix configurations by adding more `orderItems` entries (e.g. 20 qty of \$25 single-use + 80 qty of \$100 monthly). `lockCardNextUse: true` makes each card self-destruct after its first charge — the standard disbursement pattern. Bulk orders are **asynchronous** — the response is an `orderId` and `PENDING`, not cards. Save the `orderId`; it's your handle for everything that follows. Track progress with `getVirtualCardBulkOrderStatus`. Cards populate incrementally while the order is `PENDING`: ```graphql Query theme={null} query getVirtualCardBulkOrderStatus($input: GetVirtualCardBulkOrderStatusInput!) { getVirtualCardBulkOrderStatus(input: $input) { orderId orderStatus successfulCardCreations failedCardCreations totalCards virtualCards { virtualCardId cardNumber expiryMMYY cvv cardHolderName } } } ``` ```json Variables theme={null} { "input": { "orderId": "" } } ``` Poll with backoff until `orderStatus` is `COMPLETED` (or `FAILED`) — while pending, `successfulCardCreations` climbs toward `totalCards` and `virtualCards` fills in. The response contains **full PANs, CVVs, and expiry dates in plaintext**. Treat it as sensitive cardholder data: TLS only, never log it, surface it only to authorized users. A bulk order can partially succeed: `successfulCardCreations: 98, failedCardCreations: 2` on a completed order means 98 real, spendable cards and 2 that need retrying. Reconcile by comparing the counts to `totalCards`, then re-issue only the shortfall as a new order: ```json theme={null} { "input": { "offerId": "592c394e-26cc-44ac-a145-a5f81301fe77", "orderItems": [{ "quantity": 2, "spendLimit": 25.00, "lockCardNextUse": true }] } } ``` Persistent failures usually trace to program limits or funding — check [Virtual Card Error Codes](/features/virtual-card-error-codes) and your available balance. ## You're done 🎉 One hundred configured cards from one request, with a poll loop that's production-ready as-is. Next: Monitor spend across the whole batch — up to 10 cards per query, or account-wide by date. Distributing to people outside your org? Hosted claim links handle onboarding for you. **Want to learn more?** Contact us at [support@fluz.app](mailto:support@fluz.app) to speak with our experts or request a demo. # Your First Virtual Card Purchase Source: https://docs.fluz.app/quickstart/create-and-spend-with-a-virtual-card Run the full happy path end to end — pick a card program, issue a card with the right settings, reveal it, and track its spend — all in the sandbox. This Quickstart takes you from a fresh Fluz account to an issued, spendable virtual card. You'll register an app, mint a scoped access token, then browse card programs, create a card configured for your use case, reveal its details, and watch its transactions — all in the sandbox, where no real money moves. **Prerequisites** * A **Fluz account** — you'll create staging API credentials and mint an access token in Steps 1–2 below. * Requests go to the **sandbox** GraphQL endpoint. Nothing here charges a real card — see [Staging vs. Live Environment](/concepts/environments). * Every mutation needs a unique `idempotencyKey` (a client-generated UUID) so a request is only ever processed once — see [Idempotency](/concepts/idempotency). ## Before you begin Every call is a `POST` request to a single GraphQL endpoint. Minting your token (Step 2) authenticates with your API Key; every other call authenticates with your bearer token: ```bash Endpoint & headers theme={null} POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql Authorization: Bearer Content-Type: application/json ``` ```bash Example request (cURL) theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "query GetVirtualCardOffers { getVirtualCardOffers { offerId programName rewardValue } }" }' ``` Staging ships with **test card programs** ready to issue against, and your sandbox account includes a pre-added test bank card for funding. See [Test Merchants](/test-merchants) and [Test Bank Cards](/test-adding-bank-cards) for the full sandbox data set. ## The full flow Create a Fluz account at [fluz.app](https://fluz.app), then open the [Developer Console](https://uni.staging.fluzapp.com/developers) and create a new **Staging** application. When the credentials appear, copy your **API Key**, **User ID**, and **Account ID**. Full walkthrough: [Prepare your accounts](/get-started/prepare-accounts). Mint a short-lived, scoped user access token with your API Key. This call goes to the same GraphQL endpoint, authorized with `Authorization: Basic `; every later call uses the returned token as a Bearer credential. ```bash Mint token (cURL) theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Basic " \ -H "Content-Type: application/json" \ -d '{ "query": "mutation ($userId: UUID!, $accountId: UUID!, $scopes: [ScopeType!]!) { generateUserAccessToken(userId: $userId, accountId: $accountId, scopes: $scopes) { token scopes } }", "variables": { "userId": "", "accountId": "", "scopes": ["MANAGE_PAYMENT", "MAKE_DEPOSIT", "CREATE_VIRTUALCARD", "EDIT_VIRTUALCARD", "REVEAL_VIRTUALCARD"] } }' ``` ```json Response theme={null} { "data": { "generateUserAccessToken": { "token": "eyJhbGciOi...", "scopes": ["MANAGE_PAYMENT", "MAKE_DEPOSIT", "CREATE_VIRTUALCARD", "EDIT_VIRTUALCARD", "REVEAL_VIRTUALCARD"] } } } ``` Store the returned `token` and send it as `Authorization: Bearer ` on every request below. Tokens are short-lived — mint them server-side and mint a new one with the same mutation when one expires (see [Refresh an expired access token](/get-started/refresh-expired-access-token)). Full details: [API credentials](/get-started/api-credentials). Scopes gate what the token can do — include only what your flow needs: `MANAGE_PAYMENT` to fund your balance, `CREATE_VIRTUALCARD` to browse programs and issue cards, `REVEAL_VIRTUALCARD` to reveal card details and pull their transactions, and `EDIT_VIRTUALCARD` to lock, unlock, or edit cards later. Never expose your API Key in a browser or mobile client. Mint tokens server-side and forward only the token. Virtual cards are funded from your account when they're used — by default from your **Fluz balance** (`FLUZ_BALANCE`). Make sure there's enough available balance to cover the spend limit you plan to set. You can fund two ways: * **Manually**, via the [sandbox Fluz website](https://uni.staging.fluzapp.com/manage-money). * **Programmatically**, via the `depositCashBalance` mutation. Retrieve a funding source ID with `getWallet`, then deposit: ```graphql theme={null} mutation depositCashBalance($input: DepositCashBalanceInput!) { depositCashBalance(input: $input) { balances { cashBalance { availableBalance totalBalance pendingBalance } } } } ``` ```json theme={null} { "input": { "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "amount": 200.00, "depositType": "CASH_BALANCE", "bankCardId": "" } } ``` The [gift card Quickstart](/quickstart/first-gift-card) walks through this step in full detail, including retrieving payment method IDs with `getWallet`. Prefer to fund cards from a linked bank account instead? Set `primaryFundingSource: BANK_ACCOUNT` at card creation (Step 5) and pass the `bankAccountId` — no pre-funded balance needed. Every virtual card is issued against a **card program** (an "offer"): the program determines the network, issuing bank, reward rate, and the spend limits your card must stay within. Fetch the programs available to your account with `getVirtualCardOffers`. ```graphql Query theme={null} query GetVirtualCardOffers { getVirtualCardOffers { offerId programName bin bankName rewardValue programLimits { dailyLimit weeklyLimit monthlyLimit } } } ``` ```json Response theme={null} { "data": { "getVirtualCardOffers": [ { "offerId": "5b22153c-7c1e-4a66-9d2c-e786da7e9393", "programName": "Virtual Card - Mastercard Prepaid", "bin": "543210", "bankName": "Bank of Examples", "rewardValue": "1.5%", "programLimits": { "dailyLimit": "500.00", "weeklyLimit": "2000.00", "monthlyLimit": "5000.00" } } ] } } ``` Offers are sorted by `rewardValue`, so the highest-earning programs appear first. You can filter with the optional `input` — `cardType` (`DEBIT` / `PREPAID`), `cardNetwork` (`MASTERCARD` / `VISA`), and `cardBrandLocked`. Full reference: [Get Virtual Card Offers](/features/get-card-offers). In the sandbox, these test programs are always available: | Offer ID | Program Name | Reward Value | | -------------------------------------- | ---------------------------------------------- | ------------ | | `5b22153c-7c1e-4a66-9d2c-e786da7e9393` | Virtual Card - Mastercard Prepaid | 1.5% | | `e5d209d2-97b2-4636-9531-b3a459e34651` | Brand Locked Virtual Card - Mastercard Prepaid | 1.5% | | `0a6f66ec-a615-47cb-a850-ef971f7054b8` | Single Load Virtual Card - Mastercard Prepaid | 1.5% | | `490032ad-2e67-4ef0-92ff-cd7071332716` | Reloadable Virtual Card - Mastercard Prepaid | 1.5% | Not sure which program to pick? **Virtual Card** is the general-purpose, spend-anywhere program — the right default for this Quickstart. **Brand Locked** cards only work at a single merchant, **Single Load** cards are funded once and spent down, and **Reloadable** cards can be topped up after creation. See [Test Virtual Card Offers](/test-issuing-virtual-cards) for a full breakdown. Save the `offerId` you want and note its `programLimits` — the `spendLimit` you set in the next step must fit within the program's limit for your chosen duration. Issue the card with `createVirtualCard`. The input's settings are what turn a generic card into a purpose-built one — pick the pattern that matches what you're building: A card for exactly one transaction. Set `spendLimit` to the purchase amount and `lockCardNextUse: true` so the card locks itself after its first authorization — nothing else can ever be charged to it. ```json Variables theme={null} { "input": { "idempotencyKey": "07df5653-43a8-4532-9881-3ab5857bbe12", "offerId": "5b22153c-7c1e-4a66-9d2c-e786da7e9393", "spendLimit": 150.00, "lockCardNextUse": true, "cardNickname": "Vendor payment — Acme invoice #1042" } } ``` A card dedicated to one recurring charge. `spendLimitDuration: MONTHLY` resets the limit each month, so a price hike or duplicate charge beyond the cap is declined automatically. ```json Variables theme={null} { "input": { "idempotencyKey": "07df5653-43a8-4532-9881-3ab5857bbe13", "offerId": "5b22153c-7c1e-4a66-9d2c-e786da7e9393", "spendLimit": 29.99, "spendLimitDuration": "MONTHLY", "cardNickname": "SaaS — analytics subscription" } } ``` A card for a project or trip with a hard end date. `lockDate` freezes the card on that day (default is 47 months out), and disabling `usePrepaymentBalance` / `useRewardsBalance` makes it draw **only** from the specified spend account — predictable, single-source funding for clean reconciliation. ```json Variables theme={null} { "input": { "idempotencyKey": "07df5653-43a8-4532-9881-3ab5857bbe14", "offerId": "5b22153c-7c1e-4a66-9d2c-e786da7e9393", "spendLimit": 2000.00, "lockDate": "2026-09-30", "userCashBalanceId": "", "usePrepaymentBalance": false, "useRewardsBalance": false, "cardNickname": "Q3 conference travel" } } ``` All three run the same mutation: ```graphql Mutation theme={null} mutation createVirtualCard($input: CreateVirtualCardInput!) { createVirtualCard(input: $input) { virtualCardId cardholderName virtualCardLast4 expiryMonth expiryYear status cardType initialAmount usedAmount createdAt } } ``` ```json Sample response theme={null} { "data": { "createVirtualCard": { "virtualCardId": "07df5653-43a8-4532-9881-3ab5857bbe11", "cardholderName": "xyz789", "virtualCardLast4": "7890", "expiryMonth": "12", "expiryYear": "27", "status": "ACTIVE", "cardType": "MULTI_USE", "initialAmount": 150.00, "usedAmount": 0, "createdAt": "2026-07-09T10:00:00Z" } } } ``` #### The settings, at a glance The maximum the card can be charged — you're only ever charged for what's actually used. Must fit within the program's limit for your chosen duration. How the limit resets. `LIFETIME` caps total spend; `DAILY` / `WEEKLY` / `MONTHLY` make it a rolling budget — the right choice for subscriptions and team allowances. Locks the card after its first successful use — the "virtual single-use card" pattern for one-off vendor payments. `yyyy-mm-dd` date the card freezes. Time-box cards to a project, trip, or contract period. Where spend is drawn from. `FLUZ_BALANCE` uses your pre-funded balance; `BANK_ACCOUNT` pulls from a linked account (requires `bankAccountId`). By default a card may also draw from prepaid (gift card) and rewards balances. Set both to `false` for cash-only cards that draw solely from the specified `userCashBalanceId` — cleanest for accounting. Optional expense metadata attached to the resulting transaction — categories are created on first use. See [Add Expense Details](/features/add-expense-details). **Billing address:** if your account doesn't have one on file, pass a `billingAddress` (or a saved `userAddressId`). It must be a real, deliverable **US** address — no PO boxes — or creation fails with `VC-0025`. See [Address Formatting Requirements](/concepts/address-formatting-requirements). Hold on to the `virtualCardId` from the response — you'll use it to reveal the card next. If creation fails, check [Virtual Card Error Codes](/features/virtual-card-error-codes). The create response deliberately excludes the sensitive numbers. Retrieve the full PAN, CVV, and expiry with `revealVirtualCardByVirtualCardId` — this is what you (or your user) enter at a checkout or add to a mobile wallet. ```graphql Mutation theme={null} mutation RevealVirtualCard($virtualCardId: UUID!) { revealVirtualCardByVirtualCardId(virtualCardId: $virtualCardId) { cardNumber expiryMMYY cvv cardHolderName billingAddress { streetAddress postalCode city state } } } ``` ```json Variables theme={null} { "virtualCardId": "07df5653-43a8-4532-9881-3ab5857bbe11" } ``` Requires the `REVEAL_VIRTUALCARD` scope. The response contains the full card number and CVV in plaintext. Treat it as sensitive cardholder data — transmit over TLS only, never log it, and display it only to the authorized user. Most online checkouts don't need a PIN — but if your use case does (or you want tap-to-pay), see [Set Virtual Card PIN](/set-virtual-card-pin) and [Digital Wallet Push Provisioning](/digital-wallet-push-provisioning) to add the card to Apple Pay or Google Pay in one tap. Use the revealed details anywhere the card network is accepted, within the limits you set. Then pull the card's activity with `getVirtualCardTransactions` to confirm the charge — the same query powers spend dashboards, reconciliation, and decline monitoring. ```graphql Query theme={null} query GetVirtualCardTransactions { getVirtualCardTransactions( input: { virtualCardIds: ["07df5653-43a8-4532-9881-3ab5857bbe11"] filters: { transactionTypes: [PURCHASE, REFUND, DECLINE] } paginate: { limit: 20, offset: 0 } } ) { virtualCardId transactions { transactionDate transactionType transactionStatus transactionAmount merchantName mcc } } } ``` ```json Sample response theme={null} { "data": { "getVirtualCardTransactions": [ { "virtualCardId": "07df5653-43a8-4532-9881-3ab5857bbe11", "transactions": [ { "transactionDate": "2026-07-09T14:22:31Z", "transactionType": "PURCHASE", "transactionStatus": "CLEARED", "transactionAmount": 42.17, "merchantName": "ACME OFFICE SUPPLY", "mcc": 5943 } ] } ] } } ``` Requires the `REVEAL_VIRTUALCARD` scope. Omit `virtualCardIds` to pull activity across every card on the account, and filter by date range for statement-style views. Full reference: [Get Virtual Card Transactions](/features/get-virtual-card-transactions). Cards with `lockCardNextUse` or a `lockDate` handle themselves. To lock any other card on demand: ```graphql theme={null} mutation { lockVirtualCard(input: { virtualCardId: "07df5653-43a8-4532-9881-3ab5857bbe11" }) { virtualCardId locked } } ``` Requires the `EDIT_VIRTUALCARD` scope. Locking is reversible — see [Unlock Virtual Card](/features/unlock-virtual-card). ## You're done 🎉 You've issued a virtual card built for a specific job — picked a program, set spend controls, revealed the card, and tracked its activity. From here, go deeper: Change limits, nicknames, and lock dates on existing cards. Push cards into Apple Pay / Google Wallet and set PINs. Create up to 10,000 cards in a single order. Distribute cards to recipients by link, email, or SMS. **Want to learn more?** Contact us at [support@fluz.app](mailto:support@fluz.app) to speak with our experts or request a demo. # Your First Gift Card Purchase Source: https://docs.fluz.app/quickstart/first-gift-card Run the full happy path end to end — deposit funds, browse merchants, buy a gift card, and reveal it — all in the sandbox. This Quickstart takes you from a fresh Fluz account to a completed staging purchase. You'll register an app, mint a scoped access token, then deposit funds, browse the merchant catalog, purchase a gift card, and reveal its redemption details — all in the sandbox, where no real money moves. **Prerequisites** * A **Fluz account** — you'll create staging API credentials and mint an access token in Steps 1–2 below. * Requests go to the **sandbox** GraphQL endpoint. Nothing here charges a real card — see [Staging vs. Live Environment](/concepts/environments). * Every mutation needs a unique `idempotencyKey` (a client-generated UUID) so a request is only ever processed once — see [Idempotency](/concepts/idempotency). ## Before you begin Every call is a `POST` request to a single GraphQL endpoint. Minting your token (Step 2) authenticates with your API Key; every other call authenticates with your bearer token: ```bash Endpoint & headers theme={null} POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql Authorization: Bearer Content-Type: application/json ``` ```bash Example request (cURL) theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "query getWallet { getWallet { bankCards { bankCardId lastFourDigits } } }" }' ``` Your sandbox account ships with a **pre-added test bank card**, so you can run this entire flow immediately. Add more test cards or bank accounts from the [Sandbox Accounts page](https://uni.staging.fluzapp.com/accounts-and-cards) using values from the [Test Bank Cards](/test-adding-bank-cards) list. ## The full flow Five calls, each one setting up the next. The token carries the scopes; the balance funds the purchase; the merchant slug picks the offer; the purchase returns the gift card ID you reveal. ```mermaid theme={null} sequenceDiagram participant Y as Your server participant F as Fluz API Note over Y,F: Authenticate Y->>F: generateUserAccessToken (Basic API key) F->>Y: token + refreshToken + scopes Note over Y,F: Fund Y->>F: depositCashBalance (bank card or account) F->>Y: deposit PENDING, updated balance Note over Y,F: Choose Y->>F: getMerchants (name) F->>Y: merchant + slug + offers Note over Y,F: Buy Y->>F: purchaseGiftCard (slug, amount, idempotencyKey) F->>Y: purchaseDisplayId + giftCardId, status ACTIVE Note over Y,F: Redeem Y->>F: revealGiftCardByGiftCardId (giftCardId) F->>Y: code, pin, url ``` Create a Fluz account at [fluz.app](https://fluz.app), then open the [Developer Console](https://uni.staging.fluzapp.com/developers) and create a new **Staging** application. When the credentials appear, copy your **API Key**, **User ID**, and **Account ID**. Full walkthrough: [Prepare your accounts](/get-started/prepare-accounts). Mint a short-lived, scoped user access token with your API Key. This call goes to the same GraphQL endpoint, authorized with `Authorization: Basic `; every later call uses the returned token as a Bearer credential. ```bash Mint token (cURL) theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Basic " \ -H "Content-Type: application/json" \ -d '{ "query": "mutation ($userId: UUID!, $accountId: UUID!, $scopes: [ScopeType!]!) { generateUserAccessToken(userId: $userId, accountId: $accountId, scopes: $scopes) { token scopes } }", "variables": { "userId": "", "accountId": "", "scopes": ["LIST_OFFERS", "LIST_PAYMENT", "MANAGE_PAYMENT", "MAKE_DEPOSIT", "PURCHASE_GIFTCARD", "REVEAL_GIFTCARD"] } }' ``` ```json Response theme={null} { "data": { "generateUserAccessToken": { "token": "eyJhbGciOi...", "scopes": ["LIST_OFFERS", "LIST_PAYMENT", "MANAGE_PAYMENT", "MAKE_DEPOSIT", "PURCHASE_GIFTCARD", "REVEAL_GIFTCARD"] } } } ``` Store the returned `token` and send it as `Authorization: Bearer ` on every request below. Tokens are short-lived — mint them server-side and mint a new one with the same mutation when one expires (see [Refresh an expired access token](/get-started/refresh-expired-access-token)). Full details: [API credentials](/get-started/api-credentials). Scopes gate what the token can do — include only what your flow needs: `MANAGE_PAYMENT` to add funding sources and deposit, `LIST_OFFERS` to browse the catalog, and `PURCHASE_GIFTCARD` / `REVEAL_GIFTCARD` to buy and reveal. Never expose your API Key in a browser or mobile client. Mint tokens server-side and forward only the token. Pre-loading a Fluz balance generally makes gift card purchases faster and can bypass certain velocity checks. You can deposit two ways: * **Manually**, via the [sandbox Fluz website](https://uni.staging.fluzapp.com/manage-money). * **Programmatically**, via the `depositCashBalance` mutation (shown below). To deposit via API you need the ID of a funding source (e.g. a `bankCardId`). Run `getWallet` and save the ID you want to use. ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "query getWallet { getWallet { bankCards { bankCardId lastFourDigits } bankAccounts { bankAccountId } } }" }' ``` Grab the `bankCardId` or `bankAccountId` from the response. Managing funding sources via the API requires the `MANAGE_PAYMENT` scope. More detail: [View Funding Sources](/features/view-funding-sources). ### Make the deposit Use the `depositCashBalance` mutation. It takes a `DepositCashBalanceInput` object. ```graphql Mutation theme={null} mutation depositCashBalance($input: DepositCashBalanceInput!) { depositCashBalance(input: $input) { cashBalanceDeposits { cashBalanceDepositId depositDisplayId depositAmount status cashBalanceDepositType } balances { cashBalance { availableBalance totalBalance pendingBalance } giftCardCashBalance { availableBalance totalBalance pendingBalance } } } } ``` ```json Variables theme={null} { "input": { "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "amount": 100.00, "depositType": "CASH_BALANCE", "bankCardId": "88d2aea1-9461-419e-9ac7-87693b6eea1e" } } ``` #### Input fields A unique client-generated UUID that guarantees the deposit is processed only once. The amount to deposit. Destination balance. One of `CASH_BALANCE`, `GIFT_CARD_BALANCE`, or `RESERVE_BALANCE`. Where the money comes from — provide **one** of `bankAccountId`, `bankCardId`, or `paypalVaultId`. Only for `GIFT_CARD_BALANCE`. A four-digit MCC classifying the business. Use `getMccList` to retrieve valid values. When `CASH_BALANCE` is selected, the specific spend account to deposit into. ```json theme={null} { "data": { "depositCashBalance": { "cashBalanceDeposits": [ { "cashBalanceDepositId": "5f5c5b5f-4c4b-4e4e-9e9e-4f4f4f4f4f4f", "depositDisplayId": "102370", "depositAmount": "100.00", "status": "COMPLETED", "cashBalanceDepositType": "CASH_BALANCE" } ], "balances": { "cashBalance": { "availableBalance": "100.00", "totalBalance": "100.00", "pendingBalance": "0.00" }, "giftCardCashBalance": { "availableBalance": "0.00", "totalBalance": "0.00", "pendingBalance": "0.00" } } } } } ``` Deposits may settle instantly or within 2–5 business days depending on the funding source and settlement type. The `balances` object in the response reflects your current available balance. See [Check Account Balance](/check-account-balance) to re-query it at any time. With a balance ready, fetch the catalog of available merchants and their cashback offers using `getMerchants`. ```graphql Query theme={null} query GetMerchantCatalog( $paginate: OffsetInput $offerTypes: OfferTypesInput $filterBy: FilterByInput ) { getMerchants(paginate: $paginate, offerTypes: $offerTypes, filterBy: $filterBy) { merchantId name slug logoUrl faceplateUrl offers { offeringMerchantId offerId type deliveryFormat barcodeType offerRates { maxUserRewardValue cashbackVoucherRewardValue boostRewardValue denominations allowedPaymentMethods } } } } ``` ```json Variables theme={null} { "paginate": { "limit": 20, "offset": 0 }, "offerTypes": { "giftCardOffer": true, "cardLinkedOffer": false } } ``` The catalog is sorted by cashback percentage by default, so the highest offers appear first. #### Useful arguments Filter merchants by name. `{ limit, offset }`. Default and max `limit` is `20`. Boolean flags for which offer types to return, e.g. `{ giftCardOffer: true, cardLinkedOffer: false }`. Filter offers within each merchant, e.g. by `deliveryFormat` (`URL`, `CODES`, `PIN_AS_CODE`, `PIN_WITH_URL`). **Pagination:** the response may return fewer results than your `limit`. To pull the **full** catalog, keep incrementing `offset` by your `limit` and stop when the API returns an empty array (`[]`). The unfiltered catalog is large — fetch it at most once per day, and use `name` or `offerTypes` for targeted lookups. If you already know the merchant and amount, `getOfferQuote` returns the top available offer directly — including live stock info. ```graphql Query theme={null} query getOfferQuote($input: GetOfferQuoteInput!) { getOfferQuote(input: $input) { offeringMerchantId offerId type hasStockInfo denominationsType termsAndConditions offerRates { maxUserRewardValue denominations allowedPaymentMethods } stockInfo { ... on StockInfoVariableType { __typename description maxDenomination minDenomination } ... on StockInfoFixedType { __typename denomination availableStock } } } } ``` ```json Variables theme={null} { "input": { "merchantSlug": "burger-king", "denomination": 50.00, "paymentMethod": "FLUZPAY" } } ``` `merchantSlug` and `denomination` are required. `paymentMethod` defaults to `FLUZPAY` (your Fluz balance) and also accepts `BANK_CARD`, `BANK_ACCOUNT`, `PAYPAL`, `APPLE_PAY`, and `GOOGLE_PAY`. Cashback rates change regularly. Always confirm the current rate before purchasing. Use the `purchaseGiftCard` mutation. You can identify what to buy in two ways: Pass a `merchantSlug` and Fluz automatically applies the best available offer for that merchant. ```graphql Mutation theme={null} mutation purchaseGiftCard($input: PurchaseGiftCardInput!) { purchaseGiftCard(input: $input) { purchaseDisplayId purchaseAmount giftCard { giftCardId status termsAndConditions } } } ``` ```json Variables theme={null} { "input": { "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "merchantSlug": "burger-king", "amount": 50.00, "balanceAmount": 50.00 } } ``` Pass an `offerId` you selected from the catalog to purchase that exact offer. ```graphql Mutation theme={null} mutation purchaseGiftCard($input: PurchaseGiftCardInput!) { purchaseGiftCard(input: $input) { purchaseDisplayId purchaseAmount giftCard { giftCardId status termsAndConditions } } } ``` ```json Variables theme={null} { "input": { "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "offerId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19", "amount": 50.00, "balanceAmount": 50.00 } } ``` #### Input fields A unique client-generated UUID so the purchase is processed only once. Provide **one**. `merchantSlug` auto-selects the best rate; `offerId` targets a specific offer. The gift card amount to purchase. How to pay. Use `balanceAmount` (Fluz balance), `bankAccountId`, `bankCardId`, or `paypalVaultId`. You may combine your Fluz balance with another source. Falls back to your Fluz balance if another payment method fails. Set to `false` to disable that fallback. Minimum reward rate to accept when purchasing via `merchantSlug`. Forces a specific exclusive rate. Found in `getMerchants` for offers of type `EXCLUSIVE_RATE_OFFER`. The spend account (cash balance) to charge. Optional expense metadata. `memo` max 255 chars; categories are created on first use. See [Add Expense Details](/features/add-expense-details). ```json theme={null} { "data": { "purchaseGiftCard": { "purchaseDisplayId": "1019688", "purchaseAmount": 50.00, "giftCard": { "giftCardId": "85de8b3e-4e72-462c-8ed1-a6f4982e22f7", "status": "ACTIVE", "termsAndConditions": "Except as required by law, Gift Cards cannot be transferred for..." } } } } ``` Hold on to the `giftCardId` from the response — you'll use it to reveal the card in the next step. If a purchase fails, check [Gift Card Error Codes](/gift-card-error-codes). Finally, retrieve the redeemable details (code, PIN, and/or URL). Skip this if you just captured a `giftCardId` in Step 3. Otherwise, list your gift cards: ```graphql theme={null} query GetGiftCards { getGiftCards(paginate: { limit: 20, offset: 0 }) { giftCardId status createdAt deliveryFormat termsAndConditions merchant { merchantId name slug } } } ``` You can filter with `status` and `userCashBalanceId`, and page with `paginate`. ### Reveal redemption details Call `revealGiftCardByGiftCardId` with the `giftCardId`. ```graphql Mutation theme={null} mutation RevealGiftCard($giftCardId: UUID!) { revealGiftCardByGiftCardId(giftCardId: $giftCardId) { code pin url termsAndConditions } } ``` ```json Variables theme={null} { "giftCardId": "7c57c381-4b19-49e4-bbb0-404a45166ee4" } ``` ```json theme={null} { "data": { "revealGiftCardByGiftCardId": { "code": "9877890000000000", "pin": "2014", "url": null, "termsAndConditions": "Except as required by law, Gift Cards cannot be transferred for..." } } } ``` Redemption fields vary by merchant. Some cards return an alphanumeric `code` with no `pin`; others return only a `url`. Always render based on the `deliveryFormat` returned by **`getGiftCards`** (not `getMerchants`) — a merchant's active offer can change after purchase, and `getGiftCards` reflects the format the card was actually bought under. **Details not returned immediately?** Poll `revealGiftCardByGiftCardId` with exponential backoff: start at **300ms**, then double (300 → 600 → 1200 → 2400ms…) up to a max delay of **180000ms (3 minutes)**. Stop as soon as details come back. This balances responsiveness with load and avoids unnecessary timeouts. ## You're done 🎉 You've run a complete transaction — funded a balance, browsed offers, purchased a gift card, and revealed it. From here, explore the rest of the API: Issue and manage network-accepted virtual cards. Open spend accounts and move funds between them. Pull, filter, and annotate transaction history. Drop Fluz flows straight into your own UI. **Want to learn more?** Contact us at [support@fluz.app](mailto:support@fluz.app) to speak with our experts or request a demo. # Move Money Through a Wallet Source: https://docs.fluz.app/quickstart/move-money-through-a-wallet The full ledger lifecycle — create a spend account, deposit into it, transfer between accounts, withdraw back out, and verify every balance. This Quickstart runs money through the complete wallet lifecycle: open a purpose-built spend account, fund it, move funds between accounts, send them back out to an external account, and verify the ledger at each step — the plumbing under every card and purchase flow. **Prerequisites** * A **Fluz account with a staging application** — Steps 1–2 of any quickstart, or see [Prepare your accounts](/get-started/prepare-accounts) and [API credentials](/get-started/api-credentials). * A linked **test bank account** for the deposit and withdrawal legs — your sandbox ships with test funding sources, or add more from [Test Bank Accounts](/test-adding-bank-accounts). * Every money-moving mutation needs a unique `idempotencyKey` — see [Idempotency](/concepts/idempotency). ## The full flow Money enters from an external funding source, moves between spend accounts inside Fluz, and leaves the same way it came. Each arrow is one mutation, and each needs its own scope. ```mermaid theme={null} flowchart LR EXT[External bank account
or card] NEW[New spend account
Team Travel] DEF[Default spend account] EXT -->|depositCashBalance
MAKE_DEPOSIT| NEW NEW -->|transferInternalBalance
MAKE_INTERNAL_TRANSFER| DEF NEW -->|withdrawCashBalance
MAKE_WITHDRAWAL| EXT style EXT fill:#e8e8e8,stroke:#888 style NEW fill:#d4edda,stroke:#5a9 style DEF fill:#d4edda,stroke:#5a9 ``` Transfers between your own spend accounts settle immediately. Deposits and withdrawals involve an external institution, so they land as `PENDING` and settle on the network's timetable. This flow touches five scopes — one per capability: ```json theme={null} { "scopes": ["MANAGE_PAYMENT", "LIST_PAYMENT", "MAKE_DEPOSIT", "MAKE_INTERNAL_TRANSFER", "MAKE_WITHDRAWAL"] } ``` `MANAGE_PAYMENT` creates accounts and deposits, `LIST_PAYMENT` reads them, `MAKE_INTERNAL_TRANSFER` moves funds between your accounts, and `MAKE_WITHDRAWAL` sends funds out. Minting details: [API credentials](/get-started/api-credentials). Spend accounts are named sub-ledgers — "Team Travel", "Operations", "Marketing" — that keep budgets separate while living under one Fluz account. ```graphql Mutation theme={null} mutation createUserCashBalance($input: CreateUserCashBalanceInput!) { createUserCashBalance(input: $input) { userCashBalanceId nickname availableCashBalance status } } ``` ```json Variables theme={null} { "input": { "nickname": "Team Travel" } } ``` ```json Response theme={null} { "data": { "createUserCashBalance": { "userCashBalanceId": "f8a3c9e1-7b2d-4f5e-9c8a-1d2e3f4a5b6c", "nickname": "Team Travel", "availableCashBalance": "0", "status": "ACTIVE" } } } ``` Save the `userCashBalanceId` — every later step addresses the account by it. Your account also has a **default** spend account already; you'll transfer between the two in Step 4. Fund the new account from a linked source with `depositCashBalance`, targeting it via `userCashBalanceId`: ```graphql Mutation theme={null} mutation depositCashBalance($input: DepositCashBalanceInput!) { depositCashBalance(input: $input) { cashBalanceDeposits { depositAmount status } balances { cashBalance { availableBalance totalBalance pendingBalance } } } } ``` ```json Variables theme={null} { "input": { "idempotencyKey": "3c9e1f8a-2b7d-4e5f-8c9a-6b5a4f3e2d1c", "amount": 100.00, "depositType": "CASH_BALANCE", "userCashBalanceId": "", "bankAccountId": "" } } ``` Deposits may settle instantly or within 2–5 business days depending on the source; the returned `balances` reflect what's available right now. Funding source IDs come from `getWallet` — see [View Funding Sources](/features/view-funding-sources). Move \$25 from the new account into your default one with `transferInternalBalance`. Internally it's recorded as two linked movements — a withdraw from the source and a deposit into the destination — and the response returns both: ```graphql Mutation theme={null} mutation transferInternalBalance($input: TransferInternalBalanceInput!) { transferInternalBalance(input: $input) { withdraw { withdrawId amount status userCashBalanceId } cashBalanceDeposit { cashBalanceDepositId depositAmount status } } } ``` ```json Variables theme={null} { "input": { "idempotencyKey": "1f1df3e7-5d43-4e3d-83de-31922d4aefb7", "amount": 25.00, "sourceUserCashBalanceId": "", "destinationUserCashBalanceId": "" } } ``` Both IDs must be your own spend accounts, they must differ, and the source needs sufficient available balance. Sending to a **different Fluz user** is a different operation — [Transfer to Another Fluz Account](/features/transfer-to-another-fluz-wallet). Complete the round trip by sending funds to an external account with `withdrawCashBalance` — here via ACH: ```graphql Mutation theme={null} mutation withdrawCashBalance($input: WithdrawCashBalanceInput!) { withdrawCashBalance(input: $input) { withdraws { withdrawId amount status displayStatus withdrawMethod } } } ``` ```json Variables theme={null} { "input": { "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000", "amount": 50.00, "method": "BANK_ACH", "source": "CASH_BALANCE", "cashBalanceId": "", "bankAccountId": "" } } ``` `method` also accepts `BANK_CARD` (push-to-card, `bankCardId`), `PAYPAL` (`paypalVaultId`), and `VENMO` (`venmoAccountId`) — each requires its matching ID field. ACH typically settles in 1–3 business days, so expect `PENDING` / `PROCESSING` at first. Full reference: [Withdraw to External Account](/features/withdraw-to-external-account). Close the loop by reading the accounts back with `getUserCashBalances`: ```graphql theme={null} query { getUserCashBalances { userCashBalances { userCashBalanceId nickname availableCashBalance totalCashBalance lifetimeCashBalance isDefault status } totalCount } } ``` You should see the story in the numbers: "Team Travel" with `lifetimeCashBalance` of 100.00 and a reduced available balance (25 transferred out, 50 withdrawing), and your default account up 25.00. `lifetimeCashBalance` only ever grows — it's the audit trail of everything ever deposited. ## You're done 🎉 You've run the complete ledger lifecycle — create, fund, move, pay out, verify. These accounts are the substrate everything else draws on: Point `userCashBalanceId` at your new account for clean per-budget card spend. Look up a recipient and move money wallet-to-wallet. Rename, close, and administer accounts over time. Every movement above, in one filterable feed with memos and categories. **Want to learn more?** Contact us at [support@fluz.app](mailto:support@fluz.app) to speak with our experts or request a demo. # Onboard & Connect a Customer Source: https://docs.fluz.app/quickstart/onboard-customers Run the platform happy path end to end — register a customer, connect their account with OAuth, verify their identity, and operate on their behalf. This Quickstart is the "Build a platform" story made runnable. You'll register a customer programmatically, walk the OAuth grant to get a token scoped to *their* account, verify their identity with the sandbox KYC flow, and prove the connection by issuing a virtual card on their behalf — with the exact same API you use on your own account. **Prerequisites** * A **Fluz account with a staging application** — Steps 1–2 of any quickstart, or see [Prepare your accounts](/get-started/prepare-accounts) and [API credentials](/get-started/api-credentials). * An **OAuth-configured app**: a `client_id`, `client_secret`, and registered `redirect_uri` — see [Create an OAuth App](/create-an-o-auth-app). * Requests go to the **sandbox** — no real money, no real PII. See [Staging vs. Live Environment](/concepts/environments). **Registering users requires special permission.** The `registerUser` mutation is gated per application (`AUTH-0022` if not enabled) — contact your account manager to enable it. No registration access yet? Skip Step 1 and run the flow with any existing staging user. ## The full flow Provision a complete user — account, balances, and referral capability — with `registerUser`, authenticated with **your application's** token. ```graphql Mutation theme={null} mutation RegisterUser( $firstName: String! $lastName: String! $phoneNumber: String! $regionCode: String! $emailAddress: String! $dateOfBirth: String! $billingAddress: VirtualCardBillingAddressInput! $acceptCardholderAgreement: Boolean! ) { registerUser( firstName: $firstName lastName: $lastName phoneNumber: $phoneNumber regionCode: $regionCode emailAddress: $emailAddress dateOfBirth: $dateOfBirth billingAddress: $billingAddress acceptCardholderAgreement: $acceptCardholderAgreement ) { success error { code message } } } ``` ```json Variables theme={null} { "firstName": "Jane", "lastName": "Doe", "phoneNumber": "5551234567", "regionCode": "US", "emailAddress": "jane.doe@example.com", "dateOfBirth": "1990-05-15", "billingAddress": { "streetAddressLine1": "1600 Amphitheatre Pkwy", "city": "Mountain View", "state": "CA", "postalCode": "94043", "country": "United States" }, "acceptCardholderAgreement": true } ``` This mutation returns errors **in the response data**, not as GraphQL errors — always check `success` and handle the `error` object. Common failures: `AUTH-0026` / `AUTH-0027` (phone or email already in use), `AUTH-0004` (invalid phone). Full list: [User Registration](/user-registration). The customer authorizes your app by visiting the hosted grant page. Build the URL with your OAuth settings and the scopes your integration needs: ```text Authorization URL theme={null} https://uni.staging.fluzapp.com/authorize ?response_type=code &client_id= &redirect_uri= &scopes=CREATE_VIRTUALCARD%20REVEAL_VIRTUALCARD%20VERIFY_KYC &state= ``` * `scopes` is **space-delimited** (URL-encoded as `%20`) and must be a subset of the scopes enabled on your app — unknown scopes are silently ignored. * `state` is echoed back unmodified — use it to correlate the redirect with your session. The customer sees the Fluz permissions screen, approves, and is redirected to your `redirect_uri` with `?code=...&state=...`. That `code` is single-use — capture it server-side. Full parameter reference: [Client-Facing OAuth Grant Flow](/client-facing-o-auth-grant-flow). Trade the authorization code for the customer-scoped `accessToken` (plus a `refreshToken`). This call authenticates with **Basic auth**: base64 of `client_id:client_secret`. ```bash Exchange (cURL) theme={null} curl -X GET "https://uni.staging.fluzapp.com/token/exchange?code=&redirect_uri=" \ -H "Authorization: Basic " ``` ```json Response (truncated) theme={null} { "accessToken": "eyJhbGciOi...", "accessTokenExpiresAt": "2026-07-13T21:05:30.673Z", "refreshToken": "8ec16c25951616150b0332a4a6d66547", "refreshTokenExpiresAt": "2026-08-13T20:55:30.738Z", "scope": ["CREATE_VIRTUALCARD", "REVEAL_VIRTUALCARD", "VERIFY_KYC"], "user": { "id": "5070d5a1-d71a-4190-91b0-f116eec51771" } } ``` The `redirect_uri` must match the grant step **exactly**. Store the `refreshToken` and rotate before `accessTokenExpiresAt` — see [Refresh an OAuth Access Token](/refresh-o-auth-access-token). Onboarding many customers? Append your own identifier as `external_id` on the authorize URL to link Fluz accounts to records in your system — see [Managing External Reference IDs](/managing-external-reference-ids). Money movement requires a verified identity. Call `verifyUserInformation` **with the customer's token** — the token determines who gets verified. In staging, this test identity always returns `APPROVED`: This mutation requires the **`VERIFY_KYC`** scope, and `VERIFY_KYC` is not self-serve — it has to be enabled on your application by Fluz first (see [User KYC Verification](/user-kyc-verification)). Until it is, the grant page silently drops it from the requested scopes and this step fails with `AUTH-0031`. ```graphql Mutation theme={null} mutation VerifyUserInformation( $firstName: String! $lastName: String! $streetLine1: String! $streetLine2: String $city: String! $state: String! $postalCode: String! $country: String! $dateOfBirth: String! $ssnLast4: String! ) { verifyUserInformation( firstName: $firstName lastName: $lastName streetLine1: $streetLine1 streetLine2: $streetLine2 city: $city state: $state postalCode: $postalCode country: $country dateOfBirth: $dateOfBirth ssnLast4: $ssnLast4 ) { status message } } ``` ```json Variables (approved test identity) theme={null} { "firstName": "John", "lastName": "Smith", "streetLine1": "222333 Peachtree Place", "streetLine2": "", "city": "Atlanta", "state": "GA", "postalCode": "30318", "country": "United States", "dateOfBirth": "02/28/1975", "ssnLast4": "3333" } ``` **Three-attempt limit:** a user can be submitted at most 3 times before returning `ERROR` — don't burn attempts on a user you need. Full sandbox outcomes: [Testing KYC Flows](/test-kyc-flows). This is the payoff: run any Fluz operation with the **customer's token** and it executes against **their** account. `createVirtualCard` is `createVirtualCard` — no separate platform API. ```graphql theme={null} mutation { createVirtualCard( input: { idempotencyKey: "1b7de2f8-c0a9-4532-9881-3ab5857bbe15" offerId: "ed669305-5e43-40a0-9a25-7a15ed174628" spendLimit: 50.00 lockCardNextUse: true cardNickname: "First card on a connected account" } ) { virtualCardId virtualCardLast4 status } } ``` An `ACTIVE` card back means the loop is closed: registered → connected → verified → operating. The customer becomes visible in your systems through the token; they never gained access to *your* account, and you hold only the scopes they granted. ## You're done 🎉 You've onboarded and connected a customer end to end. From here: The full platform architecture — token paths, capabilities, and patterns. Let connected users request actions that owners approve. Address customers by your own IDs instead of Fluz account IDs. Onboard businesses, not just individuals. **Want to learn more?** Contact us at [support@fluz.app](mailto:support@fluz.app) to speak with our experts or request a demo. # Send a Card to Someone Source: https://docs.fluz.app/quickstart/send-a-card Generate a hosted virtual card link, watch the recipient claim it, and manage the link lifecycle — end to end in the sandbox. This Quickstart walks through the complete Send Cards lifecycle. You'll create a staging application, mint a scoped Bearer token, choose a card program, retrieve a funded spend account, generate hosted virtual card links, experience the recipient activation flow, verify issuance, then deactivate an unused link. By the end you'll have successfully completed every public Send Cards operation: * `generateVCShareLinks` * `getVCShareLinks` * `deactivateVCShareLinks` —all in the sandbox, where no real customer funds move. **Prerequisites** * A **Fluz account** — you'll create staging API credentials and mint a scoped access token in Steps 1–2 below. * Requests go to the **sandbox** GraphQL endpoint. Hosted recipient links use the standard Fluz activation experience, but all API operations use your staging environment. * Your account must have **Send Cards enabled** (`send_virtual_cards_enabled`) or every Send Cards operation returns a permissions error. * This Quickstart uses a funded **Spend Account** as the funding source. Cards are funded **when claimed**, not when links are generated. ## Before you begin Every request in this guide is sent to the Fluz GraphQL API. Minting your token (Step 2) authenticates with your API Key; every other request uses your Bearer access token. ```bash Endpoint & headers theme={null} POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql Authorization: Bearer Content-Type: application/json ``` ```bash Example request (cURL) theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query":"query { getVirtualCardOffers { offerId programName rewardValue } }" }' ``` Every GraphQL operation below uses this endpoint and your Bearer token. The only exception is Step 2, which uses the same endpoint but authenticates with your API Key (`Authorization: Basic`) to mint the token. ## The full flow This is the only flow in Get started with two participants. You generate hosted links and never touch the card itself; the **recipient** claims a link in a browser, and that claim is what issues the card. Anything unclaimed stays a liability until you deactivate it. ```mermaid theme={null} sequenceDiagram participant Y as Your server participant F as Fluz API participant R as Recipient Y->>F: generateVCShareLinks (offerId, quantity, cardLimit) F->>Y: share requests + hosted URLs Y->>R: send a link (email, SMS, your own channel) R->>F: opens fluz.app/virtual-prepaid-card/{id} Note over R,F: sign in, 2FA, set PIN, claim F->>R: card issued and revealed Y->>F: getVCShareLinks (status) F->>Y: one CLAIMED, one still ACTIVE Y->>F: deactivateVCShareLinks (unclaimed) F->>Y: link retired ``` No funds are reserved when a link is generated — the card is funded from your spend account at the moment the recipient claims it. Deactivating an unclaimed link retires it before that can happen. Create a Fluz account, then open the Developer Console and create a **Staging** application. When your application is created you'll receive: * **API Key** * **User ID** * **Account ID** Copy all three values somewhere safe. If you haven't already created a staging application, see [Prepare your accounts](/get-started/prepare-accounts). Call `generateUserAccessToken` with your API Key to generate a short-lived user access token. This token authorizes every Send Cards operation that follows. ```bash Mint access token (cURL) theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Basic " \ -H "Content-Type: application/json" \ -d '{ "query":"mutation ($userId:UUID!,$accountId:UUID!,$scopes:[ScopeType!]!){generateUserAccessToken(userId:$userId,accountId:$accountId,scopes:$scopes){token scopes}}", "variables":{ "userId":"", "accountId":"", "scopes":[ "CREATE_VIRTUALCARD", "CREATE_SHARE_LINK", "LIST_PAYMENT" ] } }' ``` ```json Response theme={null} { "data": { "generateUserAccessToken": { "token": "eyJhbGciOi...", "scopes": [ "CREATE_VIRTUALCARD", "CREATE_SHARE_LINK", "LIST_PAYMENT" ] } } } ``` Save the returned `token`. Each scope maps to one part of this flow: `CREATE_VIRTUALCARD` to browse card offers, `CREATE_SHARE_LINK` to generate, list, and deactivate share links, and `LIST_PAYMENT` to read your spend accounts. You'll use it as: ```http theme={null} Authorization: Bearer ``` for every remaining request. `CREATE_VIRTUALCARD` authorizes every Send Cards operation. This Quickstart also requests `LIST_PAYMENT` so you can retrieve the Spend Account that funds your hosted cards. **Never expose your API Key inside a browser or mobile application.** Generate Bearer tokens server-side and forward only the access token to your client. Hosted virtual cards are funded from one of your **Spend Accounts**. Unlike regular virtual cards, **no funds are reserved when the link is generated.** Instead, the selected Spend Account is charged only when the recipient successfully claims the card. Retrieve your available Spend Accounts. ```graphql Query theme={null} query GetSpendAccounts { getUserCashBalances { userCashBalances { userCashBalanceId nickname availableCashBalance isDefault } } } ``` ```json Response theme={null} { "data": { "getUserCashBalances": { "userCashBalances": [ { "userCashBalanceId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "nickname": "Primary Spend Account", "availableCashBalance": "5000.00", "isDefault": true } ] } } } ``` Save the `userCashBalanceId`. You'll supply this value when generating your hosted card links. The selected Spend Account must belong to your account and must contain sufficient funds **when the recipient claims the card**. If there isn't enough available balance at claim time, card issuance fails. Every hosted card is issued against a virtual card **offer**. The offer determines the issuing program, available rewards, and the limits that apply to every generated card. Retrieve the offers available to your account. ```graphql Query theme={null} query GetVirtualCardOffers { getVirtualCardOffers { offerId programName rewardValue programLimits { dailyLimit weeklyLimit monthlyLimit } } } ``` ```json Response theme={null} { "data": { "getVirtualCardOffers": [ { "offerId": "11111111-2222-3333-4444-555555555555", "programName": "Virtual Card", "rewardValue": "1.5%", "programLimits": { "dailyLimit": 500, "weeklyLimit": 2000, "monthlyLimit": 5000 } } ] } } ``` Save the `offerId` for the program you'd like to issue against. **The selected offer must be:** * active * share-enabled If generation fails because the offer isn't eligible for Send Cards, confirm with your Fluz representative which offers are enabled for hosted sharing. You're ready to create hosted Send Card links. Each generated link represents **one future virtual card**. At this stage: * No card has been issued. * No funds have been withdrawn. * Each link begins in the `PENDING` state. The selected Spend Account is charged **only when a recipient claims a link**. For this Quickstart you'll generate **two** links: * **Link A** — you'll claim this as the recipient. * **Link B** — you'll leave unclaimed so it can later be revoked. ```graphql Mutation theme={null} mutation GenerateVCShareLinks($input: GenerateVCShareLinksInput!) { generateVCShareLinks(input: $input) { shareLinks } } ``` ```json Variables theme={null} { "input": { "cardLimit": 25, "offerId": "", "quantity": 2, "daysUntilExpiration": 14, "shareMethod": "GENERATE_URL", "userCashBalanceId": "" } } ``` ```json Response theme={null} { "data": { "generateVCShareLinks": { "shareLinks": [ "https://fluz.app/virtual-prepaid-card/3f8ac...c1", "https://fluz.app/virtual-prepaid-card/9b2dd...77" ] } } } ``` The response returns one hosted activation URL for every generated card. Save both URLs. They're referred to below as: * **Link A** * **Link B** throughout the remainder of this Quickstart. ### Understanding the generation settings The amount loaded onto each hosted card when it's claimed. Each generated card receives its own independent limit. The hosted virtual card program to issue against. The offer must be active and enabled for Send Cards. The number of hosted links to generate. One share request and one hosted URL are created for every requested quantity. Controls how recipients receive their links. * `GENERATE_URL` returns hosted URLs for you to distribute. * `EMAIL` emails recipients automatically. * `PHONE_NUMBER` sends recipients an SMS. The Spend Account that funds every generated card. Although this field appears optional in the GraphQL schema, it is required in practice. How long recipients have to claim the hosted link. If omitted, the program default is used. The resulting expiration date also becomes the issued card's lock date. Instead of distributing hosted URLs yourself, Fluz can send every recipient their own activation link. ### Email ```json theme={null} { "input": { "cardLimit": 25, "offerId": "", "quantity": 2, "shareMethod": "EMAIL", "recipientListEmail": [ "alice@example.com", "bob@example.com" ], "userCashBalanceId": "" } } ``` ### SMS ```json theme={null} { "input": { "cardLimit": 25, "offerId": "", "quantity": 2, "shareMethod": "PHONE_NUMBER", "recipientListPhone": [ "+18883606660", "+18885551234" ], "userCashBalanceId": "" } } ``` Recipient list length **must exactly equal** `quantity`. If they don't match, validation fails and **no links are created**. Phone numbers must: * include the country code * contain no spaces * be supplied as strings Example: ```text theme={null} +18883606660 ``` **Generating hosted links does not reserve funds.** If the selected Spend Account doesn't contain sufficient available balance when the recipient claims the card, card issuance fails. **The generation response intentionally returns only the hosted URLs.** It does **not** return: * `shareRequestBatchId` * `shareRequestDisplayId` You'll retrieve those in the next step. Although you already have the hosted URLs, you'll usually also want the underlying share request records. These contain: * lifecycle status * batch identifiers * display IDs * issued virtual card IDs * expiration information Immediately after generation, retrieve your pending share requests. ```graphql Query theme={null} query GetVCShareLinks($input: GetVCShareLinksInput!) { getVCShareLinks(input: $input) { shareRequestBatchId shareRequestDisplayId shareObjectStatus linkExpirationDate virtualCardId linkUrl } } ``` ```json Variables theme={null} { "input": { "shareObjectStatuses": [ "PENDING" ] } } ``` ```json Response theme={null} { "data": { "getVCShareLinks": [ { "shareRequestBatchId": "BATCH123", "shareRequestDisplayId": "SR-000001", "shareObjectStatus": "PENDING", "virtualCardId": null, "linkExpirationDate": "2026-08-01T00:00:00Z", "linkUrl": "https://fluz.app/virtual-prepaid-card/3f8ac...c1" }, { "shareRequestBatchId": "BATCH123", "shareRequestDisplayId": "SR-000002", "shareObjectStatus": "PENDING", "virtualCardId": null, "linkExpirationDate": "2026-08-01T00:00:00Z", "linkUrl": "https://fluz.app/virtual-prepaid-card/9b2dd...77" } ] } } ``` Match each returned `linkUrl` with the URLs returned during generation. Save: * the shared `shareRequestBatchId` * both `shareRequestDisplayId` values You'll use them later when deactivating the unused link. At this point both links should have: ```text theme={null} Status: PENDING Virtual Card ID: null ``` which confirms that: * the links were created successfully * no recipient has claimed them yet * no virtual cards have been issued Open **Link A** in your browser. This is exactly the experience your recipient sees. The hosted flow guides recipients through card activation without requiring any API integration. During activation the recipient: 1. Opens the hosted activation page. 2. Signs in or creates a Fluz account. 3. Completes identity verification. 4. Completes two-factor authentication. 5. Adds a billing address if one isn't already on file. 6. Creates a card PIN. 7. Receives their hosted virtual card. Only after the final step does Fluz: * issue the virtual card * fund it from your Spend Account * assign it to the recipient The recipient becomes the authorized holder of **that card only**. They do **not** gain access to: * your Fluz account * your Spend Accounts * your balances * any other generated cards Once issued, the recipient can: * view the card * spend online * add it to Apple Pay or Google Wallet (where supported) * view future transactions **Leave Link B untouched.** You'll use it to demonstrate link revocation in the next section. After the recipient claims **Link A**, retrieve the share requests again. This time you'll see the claimed link transition from `PENDING` to `ISSUED`. ```graphql Query theme={null} query GetVCShareLinks($input: GetVCShareLinksInput!) { getVCShareLinks(input: $input) { shareRequestDisplayId shareObjectStatus virtualCardId linkUrl } } ``` ```json Variables theme={null} { "input": { "shareRequestBatchIds": [ "BATCH123" ] } } ``` ```json Response theme={null} { "data": { "getVCShareLinks": [ { "shareRequestDisplayId": "SR-000001", "shareObjectStatus": "ISSUED", "virtualCardId": "c8dbe6d8-2f2f-4c90-8d7e-15ef5b06e387", "linkUrl": "https://fluz.app/virtual-prepaid-card/3f8ac...c1" }, { "shareRequestDisplayId": "SR-000002", "shareObjectStatus": "PENDING", "virtualCardId": null, "linkUrl": "https://fluz.app/virtual-prepaid-card/9b2dd...77" } ] } } ``` Your two links should now be in different lifecycle states. | Link | Expected status | Meaning | | ---------- | --------------- | -------------------------------------------------------------------------------- | | **Link A** | `ISSUED` | The recipient successfully claimed the card and a virtual card has been created. | | **Link B** | `PENDING` | The link has not yet been claimed. | The appearance of a `virtualCardId` confirms that card issuance has completed successfully. Every hosted Send Card progresses through one of four lifecycle states. | Status | Meaning | | --------- | -------------------------------------------------------------------------- | | `PENDING` | The hosted link has been generated but hasn't yet been claimed. | | `ISSUED` | The recipient successfully claimed the link and received a virtual card. | | `USED` | The issued card has completed at least one transaction. | | `EXPIRED` | The hosted link expired naturally or was deactivated before being claimed. | Only links in the `PENDING` state can be deactivated. Once a card reaches `ISSUED`, deactivating the original link does **not** revoke the card. Suppose the second recipient never needed their card, or a batch was generated accidentally. You can revoke any **unclaimed** hosted link using either: * `shareRequestBatchIds` * `shareRequestDisplayIds` For this Quickstart you'll deactivate only **Link B** using its display ID. ```graphql Mutation theme={null} mutation DeactivateVCShareLinks($input: DeactivateVCShareLinksInput!) { deactivateVCShareLinks(input: $input) } ``` ```json Variables theme={null} { "input": { "shareRequestDisplayIds": [ "SR-000002" ] } } ``` ```json Response theme={null} { "data": { "deactivateVCShareLinks": "1 share requests successfully deactivated!" } } ``` Deactivation immediately prevents the hosted link from ever being claimed. If a recipient later opens the link they'll receive an expired or revoked experience instead of the activation flow. Deactivation only affects **unclaimed** links. If a recipient has already claimed a card (`ISSUED` or `USED`), the hosted link can no longer be used to revoke it. To stop spend on an issued card, use the appropriate virtual card lifecycle controls such as [Lock Virtual Card](/features/lock-virtual-card). Retrieve the share requests one final time. ```graphql Query theme={null} query GetVCShareLinks($input: GetVCShareLinksInput!) { getVCShareLinks(input: $input) { shareRequestDisplayId shareObjectStatus virtualCardId } } ``` ```json Variables theme={null} { "input": { "shareRequestBatchIds": [ "BATCH123" ] } } ``` ```json Response theme={null} { "data": { "getVCShareLinks": [ { "shareRequestDisplayId": "SR-000001", "shareObjectStatus": "ISSUED", "virtualCardId": "c8dbe6d8-2f2f-4c90-8d7e-15ef5b06e387" }, { "shareRequestDisplayId": "SR-000002", "shareObjectStatus": "EXPIRED", "virtualCardId": null } ] } } ``` You've now exercised the complete Send Cards lifecycle. | Link | Final status | Result | | ---------- | ------------ | ------------------------------------------------ | | **Link A** | `ISSUED` | Successfully claimed and issued to a recipient. | | **Link B** | `EXPIRED` | Successfully revoked before it could be claimed. | At this point you've successfully used all three public Send Cards operations: * ✅ `generateVCShareLinks` * ✅ `getVCShareLinks` * ✅ `deactivateVCShareLinks` ## You're done 🎉 You've completed the full hosted Send Cards lifecycle—from generation through recipient activation, lifecycle tracking, and link revocation. Along the way you: * Generated hosted virtual card links. * Selected the Spend Account that funds recipient cards. * Retrieved and tracked share request records. * Claimed a hosted card as the recipient. * Verified the transition from `PENDING` to `ISSUED`. * Revoked an unused hosted link. * Confirmed the final `EXPIRED` state. From here you can explore more advanced Send Cards workflows. Recipient experience, program rules, lifecycle states, and complete error reference. The detailed API reference for every Send Cards operation. Retrieve and manage the Spend Accounts used to fund hosted cards. Lock and manage cards after they've been issued to recipients. **Want to learn more?** Contact us at [support@fluz.app](mailto:support@fluz.app) to speak with our experts or request a demo. # Set Virtual Card PIN Source: https://docs.fluz.app/set-virtual-card-pin The `setVirtualCardPIN` mutation allows you to set a PIN on eligible virtual cards that have not had a PIN set on them yet. When creating virtual cards through the Fluz API, your cards are created without setting the PIN. Most of the time, you will not need one. However, if you'd like to set one you can use this mutation to automatically identify and update all cards that support a PIN, but haven't had their PIN set yet. **Prerequisites:** a user access token with the `CREATE_VIRTUALCARD` scope. The PIN you set must match your user PIN. This mutation adds the eligible cards to a queue for processing. The actual updating of the PINs can take several minutes — the response indicates whether the enqueuing was successful, not whether every card has been updated yet. ## Arguments * **`input`** (`SetVirtualCardPINInput!`): The input object that contains the PIN to be set on the cards. It must match your user PIN. ## Sample mutation ```graphql theme={null} mutation { setVirtualCardPIN(input: { pin: "1234" }) { success pinError } } ``` ## cURL example ```curl theme={null} curl -X POST \ https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \ -d '{ "query": "mutation { setVirtualCardPIN(input: { pin: \"1234\" }) { success pinError } }" }' ``` ## Sample response ```json theme={null} { "data": { "setVirtualCardPIN": { "success": true, "pinError": null } } } ``` ## Response fields | Field | Type | Description | | ---------- | --------- | --------------------------------------------------------------------------------- | | `success` | `Boolean` | Indicates whether the request to set PINs was successfully queued for processing. | | `pinError` | `String` | Text that will alert you if there was an error verifying your PIN. | ## Code example ## Next steps Push a virtual card into Apple Pay or Google Wallet for tap-to-pay. # Get help with Fluz Source: https://docs.fluz.app/support Reach the support team, talk to sales, or check live system status — whatever you need when you're stuck. Whether you're troubleshooting an integration, evaluating Fluz for your business, or checking whether something's down, here's where to go. Get help from the Fluz team with your account, integration, or a live issue. Talk through pricing, volume, and the right plan for your use case. Check real-time uptime and recent incidents for the Fluz API and services. # Test Adding Bank Accounts Source: https://docs.fluz.app/test-adding-bank-accounts Use these test account and routing numbers to simulate ACH / bank-account funding sources in the **staging** environment. Each pair returns a fixed status so you can verify how your integration handles both successful debits and the full range of failures. **Staging only.** These values work only in the staging environment and will be rejected in production. **You do not pass these to the GraphQL API.** No Fluz mutation accepts a routing or account number. Bank accounts are attached through Plaid: call `createPlaidLinkToken`, hand the token to Plaid Link, and complete the connection with `completePlaidLink`, which takes a `publicToken` — never the account details themselves. Enter the values below in the Plaid Link flow. See [Link external bank accounts](/features/link-external-bank-accounts) for the full sequence. ## Test accounts | Account Number | Routing Number | Status Code | | -------------- | -------------- | ------------------- | | 86063057 | 726286823 | SUCCESS | | 1234567890 | 011401533 | SUCCESS | | 86063046 | 726286823 | INSUFFICIENT\_FUNDS | | 41345938 | 726286823 | INVALID\_ACCOUNT | | 55073835 | 726286823 | UNABLE\_TO\_PROCESS | | 46319531 | 726286823 | UNAUTHORIZED\_DEBIT | | 99081536 | 726286823 | ACCOUNT\_CLOSED | ## Status codes | Status Code | What it simulates | | ------------------- | ----------------------------------------------------------------- | | SUCCESS | The account validates and the transaction completes. | | INSUFFICIENT\_FUNDS | The account is valid but lacks the funds to cover the debit. | | INVALID\_ACCOUNT | The account or routing number is not recognized. | | UNABLE\_TO\_PROCESS | A general processing failure that is not specific to the account. | | UNAUTHORIZED\_DEBIT | The debit is rejected as unauthorized by the account holder. | | ACCOUNT\_CLOSED | The account exists but has been closed. | ## Next steps Test identities that return each verification outcome — approved, declined, and duplicate. Put these test accounts to work by simulating a deposit end to end. # Test Adding Bank Cards Source: https://docs.fluz.app/test-adding-bank-cards Use these test card numbers to simulate card funding sources and card-based deposits in the **staging** environment. They let you verify your add-card, deposit, and decline-handling logic without a real card. **Staging only** These numbers are accepted only on the staging endpoint (`https://transactional-graph.staging.fluzapp.com/api/v1/graphql`). They will be rejected in production. See [Add Funding Sources](/features/add-bank-card) for how to attach a card. **How to use these values:** pass the number to `addBankCard` as `cardNumber`. CVV — any 3 digits (any 4 for American Express). Expiration date — any date in the future. Cardholder name — any non-empty value. **A billing address is required.** `addBankCard` accepts either a `billingAddress` object or a saved `userAddressId`. On an account with no address on file, omitting both fails. Use a value from [Test Addresses](/test-addresses). **The same card can only be added once.** Re-adding a number already on the account fails with a generic decline rather than a duplicate error, which is easy to misread as the test number being invalid. Check [`getWallet`](/features/view-funding-sources) before retrying, and remove the existing card with `deleteBankCard` if you want to add it again. ## Successful Transactions The cards below simulate successful card transactions. | Card Type | Card Number | CVV Code | Exp. Date | | --------------------------- | ------------------- | ------------ | --------------- | | American Express | 378282246310005 | Any 4 digits | Any future date | | American Express | 371449635398431 | Any 4 digits | Any future date | | BCcard and DinaCard | 6555900000604105 | Any 3 digits | Any future date | | Diners Club | 3056930009020004 | Any 3 digits | Any future date | | Diners Club (14-digit card) | 36227206271667 | Any 3 digits | Any future date | | Discover | 6011111111111117 | Any 3 digits | Any future date | | Discover | 6011000990139424 | Any 3 digits | Any future date | | Discover (debit) | 6011981111111113 | Any 3 digits | Any future date | | JCB | 3566002020360505 | Any 3 digits | Any future date | | Mastercard | 5555555555554444 | Any 3 digits | Any future date | | Mastercard (2-series) | 2223003122003222 | Any 3 digits | Any future date | | Mastercard (debit) | 5200828282828210 | Any 3 digits | Any future date | | Mastercard (prepaid) | 5105105105105100 | Any 3 digits | Any future date | | UnionPay | 6200000000000005 | Any 3 digits | Any future date | | UnionPay (19-digit card) | 6205500000000000004 | Any 3 digits | Any future date | | UnionPay (debit) | 6200000000000047 | Any 3 digits | Any future date | | Visa | 4242424242424242 | Any 3 digits | Any future date | | Visa (debit) | 4000056655665556 | Any 3 digits | Any future date | ## Declined Transactions The cards below simulate declined transactions. Each returns the listed error code so you can test how your integration surfaces failures. See [Decline Codes](/features/decline-codes) for the full reference. | Description | Card Number | Error Code | | -------------------------------- | ---------------- | ----------------- | | Exceeding velocity limit decline | 4000000000006975 | card\_declined | | Expired card decline | 4000000000000069 | expired\_card | | Generic decline | 4000000000000002 | card\_declined | | Incorrect CVC decline | 4000000000000127 | incorrect\_cvc | | Incorrect number decline | 4242424242424241 | incorrect\_number | | Insufficient funds decline | 4000000000009995 | card\_declined | | Lost card decline | 4000000000009987 | card\_declined | | Processing error decline | 4000000000000119 | processing\_error | | Stolen card decline | 4000000000009979 | card\_declined | ## Next steps Simulate ACH and bank-account funding sources — the other half of testing deposits. The full reference for every decline reason your integration should handle. **Want to learn more?** Contact us at [partnerships@fluz.app](mailto:partnerships@fluz.app). Speak with our experts for more info or to request a demo. # Test Addresses Source: https://docs.fluz.app/test-addresses Use these addresses to exercise the address steps of your integration in staging — virtual card issuance, bank card billing, KYC, and KYB — including your `VC-0025` error handling. Virtual card billing addresses are validated with [Smarty](https://www.smarty.com/) (USPS Delivery Point Validation). See [Address Formatting Requirements](/concepts/address-formatting-requirements) for the full rules, including which contexts are US-only. **Staging mirrors production** Address validation behaves the same in staging as in production, so any real, currently-deliverable address will pass. The examples below are provided for convenience and to trigger specific outcomes. ## US test addresses Each of these is a real, USPS-deliverable US address that Smarty validates cleanly. A real, deliverable **US** address works for **every** address field in the API — virtual cards, bank accounts, bank cards, KYC, and KYB. | `streetAddressLine1` | `city` | `state` | `postalCode` | | ---------------------- | -------------- | ------- | ------------ | | 1600 Amphitheatre Pkwy | Mountain View | CA | 94043 | | 5732 Lincoln Dr | Minneapolis | MN | 55436 | | 8084 Hogwarts Dr | Rives Junction | MI | 49277 | To test a secondary unit, add a valid apartment or suite to `streetAddressLine2` (for example, `Apt 5` or `Ste 200`). ### Example request ```json theme={null} { "input": { "billingAddress": { "streetAddressLine1": "1600 Amphitheatre Pkwy", "country": "United States", "city": "Mountain View", "state": "CA", "postalCode": "94043" } } } ``` ## International test addresses These are real, deliverable non-US addresses for testing contexts that accept international addresses. **Where international addresses are accepted** International addresses work for **bank card billing addresses**, **KYC**, and **KYB**. They are **not** accepted for **bank account (ACH) addresses** or **virtual card billing addresses** — both are US-only. Submitting an international address to a virtual card returns `VC-0025`. | `streetAddressLine1` | `city` | `state` | `postalCode` | `country` | | --------------------- | ------- | ------- | ------------ | -------------- | | 221B Baker Street | London | England | NW1 6XE | United Kingdom | | 100 Queen Street West | Toronto | ON | M5H 2N1 | Canada | | 200 George Street | Sydney | NSW | 2000 | Australia | | Unter den Linden 77 | Berlin | Berlin | 10117 | Germany | Some countries do not use a state or province. Because `state` is required on most address objects, provide the closest administrative region (for example, the province, county, or city region). ## Addresses that fail validation Use these to confirm your app handles `VC-0025` (and other rejections) gracefully. None of them will save a virtual card address. | Address | Result | Why | | ------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 2581 Oakwood Avenue, New York, NY 10605 | `VC-0025` | City / ZIP mismatch — `10605` is White Plains, NY, not New York City — and no matching delivery point. | | 896 S State St, Dover, DE 19904 | `VC-0025` | Building number `896` is not a confirmed USPS delivery point on that street and ZIP. | | 123 Fake St, Springfield, IL 62704 | `VC-0025` | Street is not present in USPS data. | | PO Box 1001, Austin, TX 78701 | Rejected | PO boxes are not accepted by the card issuer. | | 221B Baker Street, London NW1 6XE, United Kingdom | `VC-0025` | Non-US address — virtual card and bank account addresses are US-only. (Valid for bank card billing, KYC, and KYB — see the International test addresses above.) | ### Example failing request ```json theme={null} { "input": { "billingAddress": { "streetAddressLine1": "2581 Oakwood Avenue", "country": "United States", "city": "New York", "state": "NY", "postalCode": "10605" } } } ``` Returns `VC-0025`. Retrying with the same values will keep failing — the address must be corrected before it can be saved. ## Next steps You've now got test data for every part of the sandbox — cards, bank accounts, identities, merchants, and addresses. Time to put it to work. Run the full happy path end to end — deposit funds, buy a gift card, and reveal it. The full validation rules behind these outcomes, with worked examples and error handling. # Test Issuing Virtual Cards Source: https://docs.fluz.app/test-issuing-virtual-cards Ready-to-use offer IDs for issuing virtual cards in the staging environment, plus how to discover offers dynamically. Use these offer IDs to issue virtual cards in the **staging** environment. Every virtual card is created against an *offer*, which determines the card program (network, BIN, and rewards). The offer IDs below are enabled for staging and can be passed straight to `createVirtualCard`. **Complete KYC first** Virtual cards can only be issued on an account that has completed identity verification. Until the account — and, for a business, its beneficial owners — passes KYC/KYB, `getVirtualCardOffers` may return empty and `createVirtualCard` will fail. Run verification before you test. See [User KYC Verification](/user-kyc-verification) for how to submit it, and pull test identities from [Testing KYC Flows](/test-kyc-flows). **Staging only** These offer IDs are accepted only on the staging endpoint (`https://transactional-graph.staging.fluzapp.com/api/v1/graphql`). They will be rejected in production, where you must retrieve a live offer ID from [Get Virtual Card Offers](/features/get-card-offers). ## Staging offer IDs | Offer ID | Program | Issuer | Network | | -------------------------------------- | ----------------------------------------------- | ---------------- | ---------- | | `387a57ce-5fc9-400f-a610-1991d5748ea5` | Virtual Card - Mastercard Prepaid | TransPecos Banks | Mastercard | | `bc7f66ab-199c-4a39-bba8-defde1348d73` | Brand Locked Virtual Card - Mastercard Prepaid | TransPecos Banks | Mastercard | | `41b8c025-2b3c-4ede-81f7-efd34c170b76` | Single Load - Virtual Card - Mastercard Prepaid | — | Mastercard | | `490032ad-2e67-4ef0-92ff-cd7071332716` | Reloadable Virtual Card - Mastercard Prepaid | — | Mastercard | **Offer IDs change.** Staging card programs are added and retired over time, so treat the table above as a starting point rather than a fixed list. If an ID is rejected, query the current set instead: ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"query":"query { getVirtualCardOffers { offerId programName bankName } }"}' ``` Staging currently exposes more than 40 offers, including Visa and debit programs not listed here. ## Which offer should you choose? Each program issues a Mastercard prepaid card, but they differ in where the card can be spent and how it's funded. Pick the one that matches the behavior you want to test: The general-purpose program — a standard multi-use prepaid card that can be spent anywhere Mastercard is accepted, within the spend limits you set. Start here if you just want to run the happy path end to end. A card restricted to a single merchant or brand. Transactions at any other merchant are declined. Use this to test merchant-restricted spend controls — for example, a card that only works at one vendor. A card funded once, at creation. The balance is loaded up front and spent down — it can't be topped up afterward. Use this to test fixed-value, disposable card flows like one-off vendor payments or payouts. A card whose balance can be topped up after creation. Use this to test long-lived cards that get refunded repeatedly — recurring allowances, team cards, or ongoing subscription spend. ## Recommended flow Rather than hard-coding an offer ID, discover offers at runtime — the returned list reflects exactly what your account is entitled to. It's a quick two-call sequence. Call `getVirtualCardOffers` and grab an `offer_id` from the response. Pass that `offer_id` to `createVirtualCard`. A successful response returns a `virtual_card_id` with a status of `ACTIVE`. ```graphql Step 1 — Get offers theme={null} query { getVirtualCardOffers(input: {}) { offerId programName bankName rewardValue } } ``` ```graphql Step 2 — Create a card theme={null} mutation { createVirtualCard(input: { offerId: "387a57ce-5fc9-400f-a610-1991d5748ea5" spendLimit: 5.00 idempotencyKey: "3f1a9c72-5b84-4e2d-9a06-1c7d8e4b2f50" billingAddress: { streetAddressLine1: "1600 Amphitheatre Pkwy" city: "Mountain View" state: "CA" postalCode: "94043" country: "United States" } }) { virtualCardId status initialAmount } } ``` Keep `spendLimit` at or below your available balance — the card is funded from your Fluz balance, so a higher limit fails with an insufficient-balance error. A \$5 test is the safest first attempt. **A billing address is required.** If your account has none on file, `createVirtualCard` fails with *"The address on your account couldn't be verified"* — pass a `billingAddress` in the input, or a saved `userAddressId`. It must be a real, deliverable US address; a value from [Test Addresses](/test-addresses) works in staging. See [Add Virtual Card Address](/features/add-billing-address). ## Next steps The full `createVirtualCard` reference — inputs, funding, and the card lifecycle. Test identities that return each verification outcome — approved, declined, and duplicate. **Want to learn more?** Contact us at [partnerships@fluz.app](mailto:partnerships@fluz.app). Speak with our experts for more info or to request a demo. # Test KYC Flows Source: https://docs.fluz.app/test-kyc-flows Fluz requires users to be KYC verified before they can perform certain transactions or unlock higher limits. In the **staging** environment you can run the entire verification flow against known test identities and confirm that your integration handles every outcome — without using a real person's information. For the full mutation contract and response reference, see [User KYC Verification](/user-kyc-verification). **Staging only** The identities on this page exist only on the staging endpoint (`https://transactional-graph.staging.fluzapp.com/api/v1/graphql`). They are synthetic and will not verify in production. Never submit a real person's name, address, date of birth, or SSN to staging. **Before you start, you'll need:** a Fluz account with **developer access** and an active application ([Prepare Your Accounts](/get-started/prepare-accounts)), and a **user access token** for the user you're verifying ([Generate a User Access Token](/recipes/generate-user-access-token)). The token identifies which staging user the verification applies to. ## Step 1 — Create a test user (optional) If you don't already have a staging user to verify, create one with the [`registerUser`](/user-registration) mutation, then generate a user access token for it. **Restricted access** `registerUser` requires special permission from Fluz. If your application can't register users, use an existing staging user instead. Contact your account manager or [partnerships@fluz.app](mailto:partnerships@fluz.app) to enable it. ## Step 2 — Use the approved test identity The identity below is configured to return an **APPROVED** result on staging. Submit it exactly as shown. | Field | Value | | ----------------- | ---------------------- | | First name | John | | Last name | Smith | | Street address | 222333 Peachtree Place | | City | Atlanta | | State | GA | | ZIP / postal code | 30318 | | Date of birth | 02/28/1975 | | SSN | 112-22-3333 | The API only consumes the **last 4 digits** of the SSN (`ssnLast4`). For this identity that's `3333`. Date of birth is formatted as `MM/DD/YYYY`. ## Step 3 — Run the verification Call `verifyUserInformation` with the approved identity. Replace `` with the token for the user being verified. The token must carry the **`VERIFY_KYC`** scope — it is not self-serve and has to be enabled on your application by Fluz first (see [User KYC Verification](/user-kyc-verification)); without it the call fails with `AUTH-0031`. ```curl cURL theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation VerifyUserInformation { verifyUserInformation(firstName: \"John\", lastName: \"Smith\", streetLine1: \"222333 Peachtree Place\", streetLine2: \"\", city: \"Atlanta\", state: \"GA\", postalCode: \"30318\", country: \"United States\", dateOfBirth: \"02/28/1975\", ssnLast4: \"3333\") { status message } }" }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://transactional-graph.staging.fluzapp.com/api/v1/graphql", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify({ query: ` mutation VerifyUserInformation( $firstName: String! $lastName: String! $streetLine1: String! $streetLine2: String $city: String! $state: String! $postalCode: String! $country: String! $dateOfBirth: String! $ssnLast4: String! ) { verifyUserInformation( firstName: $firstName lastName: $lastName streetLine1: $streetLine1 streetLine2: $streetLine2 city: $city state: $state postalCode: $postalCode country: $country dateOfBirth: $dateOfBirth ssnLast4: $ssnLast4 ) { status message } } `, variables: { firstName: "John", lastName: "Smith", streetLine1: "222333 Peachtree Place", streetLine2: "", city: "Atlanta", state: "GA", postalCode: "30318", country: "United States", dateOfBirth: "02/28/1975", ssnLast4: "3333", }, }), } ); const data = await response.json(); console.log(data.data.verifyUserInformation); ``` ```json Approved response theme={null} { "status": "APPROVED", "message": "User verification successful" } ``` ## Step 4 — Simulate the other outcomes Use the same mutation to exercise your handling of every verification status. | Status | How to trigger it on staging | | --------- | -------------------------------------------------------------------------------------------------- | | APPROVED | Submit the approved test identity above. | | DECLINED | Submit an identity that does not match a known-good staging record. Test values: `[TBD: confirm]`. | | DUPLICATE | Re-submit identity data that matches an already-verified staging user. | | ERROR | Submit a 4th verification attempt for the same user — verification is capped at 3 tries. | The exact field values that force a `DECLINED` result on staging aren't published yet. Confirm them with the platform team before relying on them in an automated test suite. ## Verification responses | Status | Message | Description | | --------- | -------------------------------- | --------------------------------------------------- | | APPROVED | User verification successful | User is KYC verified | | DECLINED | Verification declined | User is not KYC verified | | DUPLICATE | Duplicate user verification | User is KYC verified but data matches another user | | ERROR | Exceeded user verification limit | Verification attempts exceed the maximum of 3 tries | ## Notes * **Three-attempt limit.** A user can be submitted for verification at most 3 times before returning `ERROR`. Plan your tests so you don't exhaust attempts on a user you still need. * **Tokens are per user.** The access token determines which user is verified — make sure it belongs to the test user whose identity you're submitting. ## Next steps Staging merchants with predictable offers for exercising gift card purchases. The full mutation contract — documentary verification links, statuses, and responses. # Test Gift Card Purchasing Source: https://docs.fluz.app/test-merchants The staging environment is preloaded with merchants chosen to exercise every offer shape your integration may encounter — fixed- and variable-value gift cards, boosted rates, vouchers, card-linked offers (CLOs), out-of-stock states, and group-exclusive rates. Use them to confirm your catalog, offer, and purchase flows handle each case correctly. **Staging only** These merchants and their offer configurations exist only in the staging environment. Catalogs and offers in production differ — always pull the live catalog before testing there. See [Get Merchant Catalog](/get-catalog). ## How to use these merchants Use [Get Merchant Catalog](/get-catalog), [Get Gift Card Offers](/get-gift-card-offers), or [Get Virtual Card Offers](/features/get-card-offers). The tables below map each offer shape to a staging merchant. See [Purchase Gift Cards](/purchase-gift-card) or [Create Virtual Card](/features/create-virtual-card). To test out-of-stock handling, see [Get Inventory On Stocked Offers](/get-inventory). **Legend** — ✅ verified against the staging API on 2026-08-06 · ⏳ no merchant in staging currently demonstrates this case; the row is kept so the gap is visible, but there is nothing to test against yet. Merchants change. Every ✅ row below was confirmed by querying `getMerchants` directly, but staging catalog data is rebuilt periodically — an earlier version of this page listed 24 merchants, of which 15 no longer existed. If a merchant here returns an empty array, list the current catalog rather than assuming your call is wrong: ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"query":"query { getMerchants(offerTypes: { giftCardOffer: true, cardLinkedOffer: true }) { name slug } }"}' ``` ## Gift card offers | Test case | Merchant | Slug | Staging | | ------------------------------------------------------------------------ | ----------------- | ----------------- | ------- | | Fixed value | Amazon | `amazon` | ✅ | | Fixed value, with boosted rate | TacoTime | `tacotime` | ✅ | | Fixed value, with voucher | Burger King | `burger-king` | ✅ | | Fixed value, with boosted rate **and** voucher | Chuck E. Cheese's | `chuck-e-cheeses` | ✅ | | Variable value | Shake Shack | `shake-shack` | ✅ | | Variable value, with boosted rate | Lowe's | `lowes` | ✅ | | Variable value, whole dollars only | Sephora | `sephora` | ✅ | | Variable and fixed value | Belk | `belk` | ✅ | | Variable value, with voucher | — | — | ⏳ | | Variable and fixed value, different rates per fixed denomination | — | — | ⏳ | | Variable and fixed value, different rates per denomination, with voucher | — | — | ⏳ | | Redeemable with balance only | — | — | ⏳ | | Redeemable at more than one store | — | — | ⏳ | | Delivered as a QR code | — | — | ⏳ | **Delivery formats currently in staging** are `CODES`, `URL`, `PIN_WITH_URL`, and `PIN_AS_CODE`. No merchant is configured with a QR-code delivery format, so that case cannot be exercised here today. ## Card-linked and virtual card offers These merchants carry a card-linked offer (CLO), a gift card offer, or both. Use them to test offer precedence, 0% offers, multi-program CLOs, spend-capped offers, and out-of-stock fallback. **Card-linked offers are hidden by default.** `getMerchants` returns only gift card offers unless you ask for CLOs explicitly: ```graphql theme={null} query { getMerchants(offerTypes: { giftCardOffer: true, cardLinkedOffer: true }) { name offers { type cloDetails { regularRate promoRate promoMaxCap } } } } ``` Without `offerTypes`, every offer comes back as `GIFT_CARD_OFFER` and CLOs look as though they do not exist in staging. They do — 16 merchants carry them. | Test case | Merchant | Slug | Offer types | Staging | | ----------------------------------------------------------------- | ----------- | ------------- | --------------- | ------- | | Gift card and CLO — promo rate with a spend cap | Amazon | `amazon` | Gift Card + CLO | ✅ | | Gift card and CLO — CLO is 0% | Burger King | `burger-king` | Gift Card + CLO | ✅ | | CLO only | eBay | `ebay` | CLO | ✅ | | Gift card and CLO — multiple CLOs on one merchant | Adidas | `adidas` | Gift Card + CLO | ✅ | | Gift card and CLO — gift card is the primary offer | — | — | — | ⏳ | | Gift card and CLO — CLO primary (single card program) | — | — | — | ⏳ | | Gift card and CLO — CLO primary, offer ends at lifetime spend cap | — | — | — | ⏳ | | Gift card and CLO — CLO primary, offer ends at monthly spend cap | — | — | — | ⏳ | | Gift card and CLO — different CLO per descriptor | — | — | — | ⏳ | | Gift card and CLO — gift card is out of stock | — | — | — | ⏳ | Amazon is the richest CLO fixture: `regularRate` 3%, `promoRate` 6%, `promoMaxCap` 1000. Burger King's CLO has `regularRate` 0 with a 6% promo, which is the case to use for zero-base-rate handling. ## Exclusive (group-based) rates These merchants offer an **exclusive rate** that is only visible to accounts in a specific user group. Add your test account to the listed user group to see the rate. | User group | Merchant(s) | Tier | Staging | | ------------------ | ----------------------------- | -------- | ------- | | Silver\_Business | Build-A-Bear Workshop, adidas | Silver | ✅ | | Gold\_Business | Build-A-Bear Workshop, adidas | Gold | ✅ | | Platinum\_Business | Build-A-Bear Workshop | Platinum | ✅ | ## Next steps Known-good and known-bad addresses for exercising validation on cards and KYC. Put a test merchant to work — run the purchase flow end to end. # View Gift Cards Source: https://docs.fluz.app/view-gift-card getGiftCards, revealGiftCardByGiftCardId After purchasing a gift card, you need to make an API call to reveal the gift card details. Follow these steps to retrieve the necessary information: 1. **Retrieve the Gift Card Order List** — You can skip this step if you just purchased a gift card and have the `giftCardId` already. 2. **Reveal gift card details** — Use the `revealGiftCardByGiftCardId` mutation to reveal the gift card redemption details. ## Step 1: Retrieve the Gift Card Order List > 📘 Reconciling gift cards to your orders > > `getGiftCards` returns `purchaseId` (the same UUID returned by `purchaseGiftCard` and `getUserPurchases`), `purchaseDisplayId` (the short Fluz transaction ID, e.g. `1047283`), and the order value via `purchaseValue` / `currentValue` / `currency`. Use these to map each `giftCardId` back to its originating order and amount without having to reveal every card. To obtain a list of gift cards ordered on your account, use the `getGiftCards` query. This call returns a list of gift cards with basic details, including the `giftCardId`. You will need this ID to reveal a gift card's details. ### Sample Query ```graphql theme={null} query GetGiftCards { getGiftCards( paginate: { limit: 20, offset: 0 } ) { giftCardId purchaseId purchaseDisplayId purchaserUserId endDate status purchaseValue currentValue currency createdAt deliveryFormat termsAndConditions merchant { merchantId name slug } } } ``` ### Query Arguments The following arguments are used to run this query: * `status` — If you want to filter the gift cards by status. * `userCashBalanceId` — If you want to filter gift cards by the cash balance (spend account). * `paginate` — Pagination fields * `paginate.limit` — Maximum amount of gift cards per page * `paginate.offset` — The amount of records that should be offset ### Sample Response ```json theme={null} { "data": { "getGiftCards": [ { "giftCardId": "7c57c381-4b19-49e4-bbb0-404a45166ee4", "purchaseId": "785f57ff-f756-4f5c-afca-b896881e3e87", "purchaseDisplayId": "1047283", "purchaserUserId": "7c57c381-4b19-49e4-bbb0-404a45166ee4", "endDate": "2007-12-03T10:15:30Z", "status": "ACTIVE", "purchaseValue": 25, "currentValue": 25, "currency": "USD", "createdAt": "2007-12-03T10:15:30Z", "deliveryFormat": "PIN_WITH_URL", "termsAndConditions": "Except as required by law, Gift Cards cannot be transferred for...", "merchant": { "merchantId": "123", "name": "Merchant", "slug": "merchant" } } ] } } ``` ### Response Fields `purchaseId` — The UUID of the purchase that created this gift card. Use this to reconcile a gift card against the `purchaseId` you stored when calling `purchaseGiftCard` / from `getUserPurchases`. Returns `null` if no associated purchase is found. `purchaseDisplayId` — The short, human-readable Fluz transaction ID for the purchase (e.g. `1047283`). This is the ID Fluz support references for manual review and in exports. Returns `null` if no associated purchase is found. `purchaseValue` — The value (denomination / face value) the gift card was purchased at, in the card's currency. Use this for reconciling order amounts. `currentValue` — The remaining balance on the gift card, in the card's currency. For single-use cards this typically equals `purchaseValue`. `currency` — The ISO currency code of the gift card's value (e.g. `USD`). `deliveryFormat` — The delivery format of the offer this gift card was purchased under. One of `URL`, `CODES`, `PIN_AS_CODE`, `PIN_WITH_URL`, or `CODE_WITH_PREFIX`. `termsAndConditions` — The terms and conditions text for the offer this gift card was purchased under (legal language, restrictions, expiration policies). This is the offer-time T\&C, which may differ from the merchant's currently active offer's T\&C — same reasoning as `deliveryFormat`. > 📘 Prefer `deliveryFormat` from `getGiftCards` over `getMerchants` > > A merchant's active offer can change over time. The format the gift card was purchased under may differ from the merchant's current active offer, so always use the `deliveryFormat` returned by `getGiftCards` when deciding how to render the redemption details from `revealGiftCardByGiftCardId`. ## Step 2: Reveal Gift Card Details Once you have retrieved the full gift card order list, you can reveal the details of each gift card, including the gift card code and other information, using the `revealGiftCardByGiftCardId` mutation. ### Input The `revealGiftCardByGiftCardId` mutation only requires an input argument of `giftCardId` to return your gift card redemption details as a response. ### Sample Mutation ```graphql theme={null} mutation RevealGiftCardByGiftCardId { revealGiftCardByGiftCardId( giftCardId: "86dd5fcf-fea3-4531-aa5d-1d196957f7d7" ) { code pin url termsAndConditions } } ``` ### Sample Response ```json theme={null} { "data": { "revealGiftCardByGiftCardId": { "code": "9877890000000000", "pin": "2014", "url": null, "termsAndConditions": "Except as required by law, Gift Cards cannot be transferred for..." } } } ``` > 📘 Not all merchant gift cards have codes, pins and URLs > > Depending on the merchant and the partner, the gift card details may vary. Some merchants use an alphanumeric code and do not provide a PIN with their gift card. Some merchants or partners only provide a URL instead of a code and pin. > > Fluz will always pass all information that we are provided from the merchants on each gift card order. ## Recommended Polling Strategy If gift card details are not immediately returned, you can poll the Reveal Gift Card endpoint using an exponential backoff strategy. Starting recommendation: * **Initial delay:** 300ms * **Backoff strategy:** Exponential (e.g. 300ms → 600ms → 1200ms → 2400ms → …) * **Maximum delay:** 180000ms (3 minutes) * Stop polling once gift card details are successfully returned This approach balances responsiveness with system load and helps prevent unnecessary timeouts or excessive requests. # API Connectors Source: https://docs.fluz.app/API-connectors-overview Point an integration you have already built at Fluz — without rewriting it. If you already send gift card orders to Tango, Runa, or InComm, you do not have to rebuild anything to buy from Fluz. Change your base URL, change your credential, and keep the code you already wrote. That is what an **API adapter** does. ## What an API adapter is We mapped our API calls onto theirs. Every operation your vendor exposes — create an order, check an order, read a balance — has a Fluz operation behind it, and the adapter sits in between doing the translation in both directions. The result is that the integration you already built against Tango, Runa, or InComm now buys from Fluz instead. You keep your request shapes, and a successful response comes back in the shape your parser already expects. Two things are Fluz's rather than your vendor's: error responses and order status values. Both are covered in [Connector behaviour](/connector-behaviour). ![How the Fluz API Adapter works](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTgwIDY2MCIgd2lkdGg9IjExODAiIGhlaWdodD0iNjYwIiBmb250LWZhbWlseT0iUG9wcGlucywgSW50ZXIsIC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJ1NlZ29lIFVJJywgc2Fucy1zZXJpZiI+CiAgPGRlZnM+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9ImJyYW5kIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM4NEJFRkYiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjMjFCQTRDIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPG1hcmtlciBpZD0iYXJyb3ciIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iOSIgcmVmWT0iNSIgbWFya2VyV2lkdGg9IjciIG1hcmtlckhlaWdodD0iNyIgb3JpZW50PSJhdXRvLXN0YXJ0LXJldmVyc2UiPgogICAgICA8cGF0aCBkPSJNIDAgMCBMIDEwIDUgTCAwIDEwIHoiIGZpbGw9IiM5QTk2OEEiLz4KICAgIDwvbWFya2VyPgogICAgPG1hcmtlciBpZD0iYXJyb3dHcmVlbiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI5IiByZWZZPSI1IiBtYXJrZXJXaWR0aD0iNyIgbWFya2VySGVpZ2h0PSI3IiBvcmllbnQ9ImF1dG8tc3RhcnQtcmV2ZXJzZSI+CiAgICAgIDxwYXRoIGQ9Ik0gMCAwIEwgMTAgNSBMIDAgMTAgeiIgZmlsbD0iIzBFN0EzNCIvPgogICAgPC9tYXJrZXI+CiAgPC9kZWZzPgoKICA8cmVjdCB3aWR0aD0iMTE4MCIgaGVpZ2h0PSI2NjAiIGZpbGw9IiNGQkY5RjUiLz4KICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTE4MCIgaGVpZ2h0PSI1IiBmaWxsPSJ1cmwoI2JyYW5kKSIvPgoKICA8IS0tIFRpdGxlIC0tPgogIDx0ZXh0IHg9IjYwIiB5PSI3MiIgZm9udC1zaXplPSIyNyIgZm9udC13ZWlnaHQ9IjYwMCIgZmlsbD0iIzE0MTQwRiI+V2hhdCBhbiBBUEkgYWRhcHRlciBkb2VzPC90ZXh0PgogIDx0ZXh0IHg9IjYwIiB5PSIxMDIiIGZvbnQtc2l6ZT0iMTUiIGZpbGw9IiM2QjZCNjAiPllvdXIgaW50ZWdyYXRpb24ga2VlcHMgc3BlYWtpbmcgdGhlIHZlbmRvciYjODIxNztzIGxhbmd1YWdlLiBGbHV6IGRvZXMgdGhlIHRyYW5zbGF0aW5nLjwvdGV4dD4KCiAgPCEtLSA9PT09PT09PT09PT0gUk9XIDEgOiBUT0RBWSA9PT09PT09PT09PT0gLS0+CiAgPHJlY3QgeD0iNjAiIHk9IjE0MCIgd2lkdGg9IjEwNjAiIGhlaWdodD0iMTg4IiByeD0iMTQiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iI0U3RTNEOSIgc3Ryb2tlLXdpZHRoPSIxIi8+CiAgPHRleHQgeD0iODgiIHk9IjE3NCIgZm9udC1zaXplPSIxMiIgZm9udC13ZWlnaHQ9IjYwMCIgZmlsbD0iIzlBOTY4QSIgbGV0dGVyLXNwYWNpbmc9IjEuNiI+VE9EQVk8L3RleHQ+CgogIDwhLS0gYXBwIC0tPgogIDxyZWN0IHg9Ijg4IiB5PSIxOTYiIHdpZHRoPSIyNTIiIGhlaWdodD0iOTQiIHJ4PSIxMCIgZmlsbD0iI0Y0RjFFQSIgc3Ryb2tlPSIjREREOENDIiBzdHJva2Utd2lkdGg9IjEiLz4KICA8dGV4dCB4PSIyMTQiIHk9IjIzOCIgZm9udC1zaXplPSIxNiIgZm9udC13ZWlnaHQ9IjYwMCIgZmlsbD0iIzE0MTQwRiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+WW91ciBhcHBsaWNhdGlvbjwvdGV4dD4KICA8dGV4dCB4PSIyMTQiIHk9IjI2MiIgZm9udC1zaXplPSIxMyIgZmlsbD0iIzZCNkI2MCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+QWxyZWFkeSBidWlsdC4gQWxyZWFkeSBsaXZlLjwvdGV4dD4KCiAgPGxpbmUgeDE9IjM0OCIgeTE9IjI0MyIgeDI9IjQyNCIgeTI9IjI0MyIgc3Ryb2tlPSIjOUE5NjhBIiBzdHJva2Utd2lkdGg9IjEuNiIgbWFya2VyLWVuZD0idXJsKCNhcnJvdykiLz4KICA8dGV4dCB4PSIzODYiIHk9IjIzMCIgZm9udC1zaXplPSIxMS41IiBmaWxsPSIjOUE5NjhBIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj52ZW5kb3Itc2hhcGVkPC90ZXh0PgogIDx0ZXh0IHg9IjM4NiIgeT0iMjQ1IiBmb250LXNpemU9IjExLjUiIGZpbGw9IiM5QTk2OEEiIHRleHQtYW5jaG9yPSJtaWRkbGUiPnJlcXVlc3Q8L3RleHQ+CgogIDwhLS0gdmVuZG9yIGFwaSAtLT4KICA8cmVjdCB4PSI0MzIiIHk9IjE5NiIgd2lkdGg9IjI1MiIgaGVpZ2h0PSI5NCIgcng9IjEwIiBmaWxsPSIjRjRGMUVBIiBzdHJva2U9IiNEREQ4Q0MiIHN0cm9rZS13aWR0aD0iMSIvPgogIDx0ZXh0IHg9IjU1OCIgeT0iMjM4IiBmb250LXNpemU9IjE2IiBmb250LXdlaWdodD0iNjAwIiBmaWxsPSIjMTQxNDBGIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5UYW5nbyAvIFJ1bmEgLyBJbkNvbW08L3RleHQ+CiAgPHRleHQgeD0iNTU4IiB5PSIyNjIiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiM2QjZCNjAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlRoZWlyIEFQSTwvdGV4dD4KCiAgPGxpbmUgeDE9IjY5MiIgeTE9IjI0MyIgeDI9Ijc2OCIgeTI9IjI0MyIgc3Ryb2tlPSIjOUE5NjhBIiBzdHJva2Utd2lkdGg9IjEuNiIgbWFya2VyLWVuZD0idXJsKCNhcnJvdykiLz4KCiAgPCEtLSBzdXBwbHkgLS0+CiAgPHJlY3QgeD0iNzc2IiB5PSIxOTYiIHdpZHRoPSIyNTIiIGhlaWdodD0iOTQiIHJ4PSIxMCIgZmlsbD0iI0Y0RjFFQSIgc3Ryb2tlPSIjREREOENDIiBzdHJva2Utd2lkdGg9IjEiLz4KICA8dGV4dCB4PSI5MDIiIHk9IjIzOCIgZm9udC1zaXplPSIxNiIgZm9udC13ZWlnaHQ9IjYwMCIgZmlsbD0iIzE0MTQwRiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+VGhlaXIgZ2lmdCBjYXJkIHN1cHBseTwvdGV4dD4KICA8dGV4dCB4PSI5MDIiIHk9IjI2MiIgZm9udC1zaXplPSIxMyIgZmlsbD0iIzZCNkI2MCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+VGhlaXIgcmF0ZXM8L3RleHQ+CgogIDwhLS0gPT09PT09PT09PT09IFJPVyAyIDogV0lUSCBGTFVaID09PT09PT09PT09PSAtLT4KICA8cmVjdCB4PSI2MCIgeT0iMzYwIiB3aWR0aD0iMTA2MCIgaGVpZ2h0PSIxODgiIHJ4PSIxNCIgZmlsbD0iI0ZGRkZGRiIgc3Ryb2tlPSIjQzhFOEQyIiBzdHJva2Utd2lkdGg9IjEuNSIvPgogIDxyZWN0IHg9IjYwIiB5PSIzNjAiIHdpZHRoPSIxMDYwIiBoZWlnaHQ9IjQiIHJ4PSIyIiBmaWxsPSJ1cmwoI2JyYW5kKSIvPgogIDx0ZXh0IHg9Ijg4IiB5PSIzOTQiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSI2MDAiIGZpbGw9IiMwRTdBMzQiIGxldHRlci1zcGFjaW5nPSIxLjYiPldJVEggVEhFIEZMVVogQVBJIEFEQVBURVI8L3RleHQ+CgogIDwhLS0gYXBwICh1bmNoYW5nZWQpIC0tPgogIDxyZWN0IHg9Ijg4IiB5PSI0MTYiIHdpZHRoPSIyNTIiIGhlaWdodD0iOTQiIHJ4PSIxMCIgZmlsbD0iI0Y0RjFFQSIgc3Ryb2tlPSIjREREOENDIiBzdHJva2Utd2lkdGg9IjEiLz4KICA8dGV4dCB4PSIyMTQiIHk9IjQ1MiIgZm9udC1zaXplPSIxNiIgZm9udC13ZWlnaHQ9IjYwMCIgZmlsbD0iIzE0MTQwRiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+WW91ciBhcHBsaWNhdGlvbjwvdGV4dD4KICA8cmVjdCB4PSIxNjQiIHk9IjQ2NiIgd2lkdGg9IjEwMCIgaGVpZ2h0PSIyNCIgcng9IjEyIiBmaWxsPSIjRTNGNEU4Ii8+CiAgPHRleHQgeD0iMjE0IiB5PSI0ODIiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSI2MDAiIGZpbGw9IiMwRTdBMzQiIHRleHQtYW5jaG9yPSJtaWRkbGUiPnVuY2hhbmdlZDwvdGV4dD4KCiAgPGxpbmUgeDE9IjM0OCIgeTE9IjQ2MyIgeDI9IjQyNCIgeTI9IjQ2MyIgc3Ryb2tlPSIjMEU3QTM0IiBzdHJva2Utd2lkdGg9IjEuNiIgbWFya2VyLWVuZD0idXJsKCNhcnJvd0dyZWVuKSIvPgogIDx0ZXh0IHg9IjM4NiIgeT0iNDUwIiBmb250LXNpemU9IjExLjUiIGZpbGw9IiMwRTdBMzQiIHRleHQtYW5jaG9yPSJtaWRkbGUiPnNhbWU8L3RleHQ+CiAgPHRleHQgeD0iMzg2IiB5PSI0NjUiIGZvbnQtc2l6ZT0iMTEuNSIgZmlsbD0iIzBFN0EzNCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+cmVxdWVzdDwvdGV4dD4KCiAgPCEtLSBhZGFwdGVyIC0tPgogIDxyZWN0IHg9IjQzMiIgeT0iNDE2IiB3aWR0aD0iMjUyIiBoZWlnaHQ9Ijk0IiByeD0iMTAiIGZpbGw9IiMwRTdBMzQiLz4KICA8dGV4dCB4PSI1NTgiIHk9IjQ1MiIgZm9udC1zaXplPSIxNiIgZm9udC13ZWlnaHQ9IjYwMCIgZmlsbD0iI0ZGRkZGRiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Rmx1eiBBUEkgQWRhcHRlcjwvdGV4dD4KICA8dGV4dCB4PSI1NTgiIHk9IjQ3NiIgZm9udC1zaXplPSIxMyIgZmlsbD0iI0I3RThDNiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+dHJhbnNsYXRlcyBpbiwgdHJhbnNsYXRlcyBiYWNrPC90ZXh0PgoKICA8bGluZSB4MT0iNjkyIiB5MT0iNDYzIiB4Mj0iNzY4IiB5Mj0iNDYzIiBzdHJva2U9IiMwRTdBMzQiIHN0cm9rZS13aWR0aD0iMS42IiBtYXJrZXItZW5kPSJ1cmwoI2Fycm93R3JlZW4pIi8+CgogIDwhLS0gZmx1eiBzdXBwbHkgLS0+CiAgPHJlY3QgeD0iNzc2IiB5PSI0MTYiIHdpZHRoPSIyNTIiIGhlaWdodD0iOTQiIHJ4PSIxMCIgZmlsbD0iI0Y0RjFFQSIgc3Ryb2tlPSIjREREOENDIiBzdHJva2Utd2lkdGg9IjEiLz4KICA8dGV4dCB4PSI5MDIiIHk9IjQ1MiIgZm9udC1zaXplPSIxNiIgZm9udC13ZWlnaHQ9IjYwMCIgZmlsbD0iIzE0MTQwRiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Rmx1eiBnaWZ0IGNhcmQgc3VwcGx5PC90ZXh0PgogIDx0ZXh0IHg9IjkwMiIgeT0iNDc2IiBmb250LXNpemU9IjEzIiBmaWxsPSIjNkI2QjYwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5GbHV6IHJhdGVzPC90ZXh0PgoKICA8IS0tID09PT09PT09PT09PSBGT09URVI6IHdoYXQgeW91IGNoYW5nZSA9PT09PT09PT09PT0gLS0+CiAgPHRleHQgeD0iNjAiIHk9IjU5NiIgZm9udC1zaXplPSIxMyIgZm9udC13ZWlnaHQ9IjYwMCIgZmlsbD0iIzlBOTY4QSIgbGV0dGVyLXNwYWNpbmc9IjEuMiI+QUxMIFlPVSBDSEFOR0U8L3RleHQ+CgogIDxyZWN0IHg9IjYwIiB5PSI2MDgiIHdpZHRoPSIzMzYiIGhlaWdodD0iMzYiIHJ4PSI4IiBmaWxsPSIjRkZGRkZGIiBzdHJva2U9IiNFN0UzRDkiLz4KICA8dGV4dCB4PSI3OCIgeT0iNjMxIiBmb250LXNpemU9IjEzLjUiIGZpbGw9IiMxNDE0MEYiPjx0c3BhbiBmb250LXdlaWdodD0iNjAwIj4xLjwvdHNwYW4+ICBUaGUgYmFzZSBVUkw8L3RleHQ+CgogIDxyZWN0IHg9IjQyMiIgeT0iNjA4IiB3aWR0aD0iMzM2IiBoZWlnaHQ9IjM2IiByeD0iOCIgZmlsbD0iI0ZGRkZGRiIgc3Ryb2tlPSIjRTdFM0Q5Ii8+CiAgPHRleHQgeD0iNDQwIiB5PSI2MzEiIGZvbnQtc2l6ZT0iMTMuNSIgZmlsbD0iIzE0MTQwRiI+PHRzcGFuIGZvbnQtd2VpZ2h0PSI2MDAiPjIuPC90c3Bhbj4gIFRoZSBBdXRob3JpemF0aW9uIGhlYWRlcjwvdGV4dD4KCiAgPHJlY3QgeD0iNzg0IiB5PSI2MDgiIHdpZHRoPSIzMzYiIGhlaWdodD0iMzYiIHJ4PSI4IiBmaWxsPSIjRkZGRkZGIiBzdHJva2U9IiNFN0UzRDkiLz4KICA8dGV4dCB4PSI4MDIiIHk9IjYzMSIgZm9udC1zaXplPSIxMy41IiBmaWxsPSIjMTQxNDBGIj48dHNwYW4gZm9udC13ZWlnaHQ9IjYwMCI+My48L3RzcGFuPiAgWW91ciBicmFuZCBpZGVudGlmaWVyczwvdGV4dD4KPC9zdmc+Cg==) Think of a travel plug adapter. The appliance does not change. The wall socket does not change. Something in between makes the two fit. ## How the mapping works Your request arrives shaped like the vendor's. The adapter translates the route and the fields into the equivalent Fluz operation, executes it against Fluz supply, then translates the result back into the response shape your code already expects. Request and response mapping through the Fluz API Adapter ### Operation mapping Each connector page publishes the full table for its vendor. Tango Card, as an example: | Operation | Your existing call | Fluz endpoint | | :------------------------ | :--------------------------------- | :--------------------------------- | | Create order | `POST /v2/orders/` | `POST /v2/orders/` | | Get order | `GET /v2/orders/:referenceOrderID` | `GET /v2/orders/:referenceOrderID` | | Get all orders | `GET /v2/orders` | `GET /v2/orders` | | Get balance, all accounts | `GET /v2/accounts` | `GET /v2/accounts` | | Get balance, one account | `GET /v2/accounts/:accountId` | `GET /v2/accounts/:id` | Paths and verbs are preserved deliberately. The point of the adapter is that your routing layer does not have to know anything changed. ### Field mapping Tango Card again, as an example: | Field in your request | What Fluz does with it | | :-------------------- | :----------------------------------------------------------------------------------------------------------------------------- | | `utid` / brand code | **Resolved against the Fluz catalog. See the warning below** | | `amount` | Purchase amount | | `externalRefID` | Accepted. Correlate the order using `referenceOrderID` from the response | | `accountIdentifier` | Orders are funded from the account your API key is issued for. Request one key per account if you split funding across several | | `customerIdentifier` | Kept for compatibility with your existing request shape | **Tango and InComm return complete results synchronously.** The call does not return until the purchase has finished, which can take up to 150 seconds, so set your client's read timeout above that. Runa is asynchronous by default: it returns a reference ID immediately and you read the order once it completes. Send `X-Execution-Mode: sync` to wait for the full result instead. Both are covered in [Connector behaviour](/connector-behaviour). ### Per-vendor request constraints Your vendor's request shape is accepted as it stands, but a few values are fixed. Email delivery to the recipient must be off, orders are single-recipient and single-product, and Runa orders must be funded from the account balance in USD. A request that breaks one of these is rejected. The full list is on [Connector behaviour](/connector-behaviour). Read it alongside your connector page's request examples before you cut over. ## What changes, and what does not | | Before | After | | :--------------------------------------------------- | :--------------- | :------------------------------------ | | Request shapes | Vendor's schema | **Unchanged** | | Successful response shapes | Vendor's schema | **Unchanged** | | Base URL | Vendor's host | Fluz adapter host | | Credential | Vendor's key | Your Fluz API key | | Order status values | Vendor's | **Fluz's. Update your status checks** | | Error responses | Vendor's schema | **Fluz's envelope** | | Brand identifiers (`utid`, brand slug, product code) | Vendor's catalog | **Fluz's catalog — must be remapped** | | Rates and discounts | Vendor's | Fluz's | | Supplier of record, invoicing, reconciliation | Vendor | Fluz | **Brand identifiers are the one thing you must change.** The adapter does not translate the vendor's product catalog into Fluz's. A Tango `utid` of `U561593` is not the same card as a Fluz `utid` of `1800FL-US`. Map your brand list against the Fluz catalog before you cut over, or your orders will succeed and deliver the wrong card. ## Switching in three steps Point your existing client at the Fluz adapter host for your vendor. For Tango Card that is `https://api-adapter.staging.fluzapp.com/tangocard` in staging and `https://api-adapter.fluzapp.com/tangocard` in production, instead of `https://integration-api.tangocard.com/rass`. Both hosts are listed on every connector page. Every connector uses HTTP Basic authentication with your Fluz API key: `Authorization: Basic `. Drop the vendor's username and password. Swap your vendor product codes for their Fluz equivalents. Everything else — paths, verbs, request bodies, response parsing — stays as it is. ## Available connectors **Documented and available.** Tango Card, Runa, InComm — each with a published endpoint comparison and request examples. **Beta.** Stripe, Worldpay, TSYS, Checkout, Braintree, TabaPay, Finix, Authorize.net, Sola / Cardknox. Contact sales for early access and endpoint details. **Beta.** Venmo and PayPal. Contact sales for early access and endpoint details. Gift card connectors are the mature family and the only one with published operation-level documentation today. Each gift card connector page lists the exact operations that are implemented, alongside the original vendor endpoint they correspond to. ## Coverage is per-operation **The adapter implements the operations listed on each connector page, and only those.** Each one is a purpose-built translation for that operation rather than a blanket pass-through. If an operation does not appear in the endpoint comparison table on a connector page, it has not been built yet. Before you begin, list every vendor endpoint your integration calls today and check each one against the connector page. If something you depend on is missing, tell your Fluz contact before you start — most gaps are small additions, but they need to be scoped. ## Connectors are separate from the Fluz GraphQL API This trips people up, so it is worth stating plainly. Fluz has two distinct developer surfaces, and they do not share credentials. | | API Connectors | Fluz API | | :---------------- | :--------------------------------------- | :---------------------------------------------------- | | Protocol | REST, shaped like the vendor | GraphQL | | Authentication | Basic, with a Fluz API key | Bearer, with a User Access Token | | Credential source | Issued by Fluz on request | Self-serve in the Developers section of the dashboard | | Scope | The operations your old vendor supported | The full Fluz platform | **Connector keys are not dashboard keys.** The API key you generate in the Developers section of the Fluz dashboard will not authenticate against a connector, and vice versa. Connector credentials are provisioned by Fluz. Contact your Fluz representative to request one. ## When to use a connector, and when not to You have a working vendor integration in production, you want Fluz supply and Fluz rates quickly, and you do not want to spend an engineering cycle on the switch. Time to live is measured in days. You want the full platform: virtual cards, wallets and spend accounts, transaction data, webhooks, cashback rates, offer quoting, and the complete merchant catalog. None of that is reachable through a vendor-shaped interface. A common path is to start with a connector to get volume flowing, then migrate to the [Fluz API](/overview) as you adopt capabilities the old vendor never had. ## Getting access API Connectors are currently offered as a beta program. Contact your Fluz representative or email sales to request connector credentials and confirm operation coverage for your vendor. ## Pick your vendor Orders and account balances. Basic auth, synchronous responses. Orders and currency balances. Synchronous and asynchronous execution modes. Immediate orders, order detail, card retrieval, and program balance. # API overview Source: https://docs.fluz.app/api-reference/overview Endpoints, request format, and how to read responses from the Fluz GraphQL API. The Fluz API is a single GraphQL endpoint per environment. This page is the reference companion to [How the GraphQL API works](/concepts/graphql). ## Browse the reference Read operations — fetch wallets, transactions, merchants, card offers, and account data. Write operations — issue cards, move money, register users and businesses, and manage approvals. ### Types Response shapes returned by queries and mutations. Arguments you pass to operations. Fixed sets of allowed values, like statuses and card networks. Fields that resolve to one of several object types. Shared field contracts implemented by multiple objects. Primitive values like DateTime and UUID. ### Conventions The Authorization header, token endpoints, and the OAuth authorization redirect. The error envelope, domain code prefixes, and retry guidance. Per-surface request limits and what a 429 looks like. Offset inputs, page-size caps, and connection responses. One operation lives outside GraphQL — the file upload used for sole proprietorship registration. ## Endpoints | Environment | GraphQL endpoint | | ----------- | ---------------------------------------------------------------- | | Staging | `https://transactional-graph.staging.fluzapp.com/api/v1/graphql` | | Live | `https://transactional-graph.fluzapp.com/api/v1/graphql` | Access tokens are minted at the same endpoint via the `generateUserAccessToken` mutation, authorized with your API Key — see [Authentication](/api-reference/authentication). ## Headers | Header | Required | Notes | | --------------- | -------- | ---------------------------------------------------------------------------------- | | `Authorization` | Yes | `Bearer ` (`Basic ` when calling `generateUserAccessToken`) | | `Content-Type` | Yes | `application/json` | ## Response envelope ```json theme={null} { "data": { ... }, "errors": [ ... ] } ``` * `data` — the successful payload (may be partial on error). * `errors` — array of failures, each with `code`, `message`, and `path`. ## Schema Staging exposes introspection so you can point tooling (Apollo Studio, GraphiQL, codegen) at it directly. In live, request the current SDL from your account team. # approvalRequests Source: https://docs.fluz.app/api-reference/queries/approval-requests Lists open approval requests for the caller's account. Lists open approval requests for the caller's account. ```graphql theme={null} query { approvalRequests: [ApprovalRequest!]! } ``` ## Returns [`[ApprovalRequest!]!`](/api-reference/types/approval-request) — An open approval request for the caller's account. # authorizedUsers Source: https://docs.fluz.app/api-reference/queries/authorized-users List authorized users on the caller's account. List authorized users on the caller's account. All filters are optional and narrow the results by email and/or phone. The target account is always resolved from the caller's credentials - Bearer tokens use the token's accountId; Basic (API key) callers use the application's configured operator account. OWNER role assignments are excluded from results. Requires the VIEW\_SUBUSERS scope. Supports both Bearer and Basic auth. ```graphql theme={null} query { authorizedUsers( email: String phone: String ): [AuthorizedUser] } ``` ## Arguments Filter by the email address of the authorized user. Filter by the phone number of the authorized user. ## Returns [`[AuthorizedUser]`](/api-reference/types/authorized-user) — Represents an authorized user on an account. # erpChartOfAccounts Source: https://docs.fluz.app/api-reference/queries/erp-chart-of-accounts The imported chart of accounts for the account's connected ERP provider. The imported chart of accounts for the account's connected ERP provider. ```graphql theme={null} query { erpChartOfAccounts( filter: ErpReferenceItemFilter paginate: OffsetInput ): ErpReferenceItemConnection! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`ErpReferenceItemConnection!`](/api-reference/types/erp-reference-item-connection) # erpCustomers Source: https://docs.fluz.app/api-reference/queries/erp-customers The imported customers for the account's connected ERP provider. The imported customers for the account's connected ERP provider. ```graphql theme={null} query { erpCustomers( filter: ErpReferenceItemFilter paginate: OffsetInput ): ErpReferenceItemConnection! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`ErpReferenceItemConnection!`](/api-reference/types/erp-reference-item-connection) # erpTransactionMetadata Source: https://docs.fluz.app/api-reference/queries/erp-transaction-metadata The ERP metadata for a single transaction. The ERP metadata for a single transaction. Null when the account has no active ERP connection or when this transaction has no ERP metadata yet. ```graphql theme={null} query { erpTransactionMetadata( transactionRecordId: UUID! ): ErpTransactionMetadata } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`ErpTransactionMetadata`](/api-reference/types/erp-transaction-metadata) — The current ERP-side categorization state of a transaction. # erpTransactionMetadataList Source: https://docs.fluz.app/api-reference/queries/erp-transaction-metadata-list ERP metadata across transactions, filterable by sync status / last-updated time. ERP metadata across transactions, filterable by sync status / last-updated time. ```graphql theme={null} query { erpTransactionMetadataList( filter: ErpTransactionMetadataFilter paginate: OffsetInput ): ErpTransactionMetadataConnection! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`ErpTransactionMetadataConnection!`](/api-reference/types/erp-transaction-metadata-connection) # erpVendors Source: https://docs.fluz.app/api-reference/queries/erp-vendors The imported vendors for the account's connected ERP provider. The imported vendors for the account's connected ERP provider. ```graphql theme={null} query { erpVendors( filter: ErpReferenceItemFilter paginate: OffsetInput ): ErpReferenceItemConnection! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`ErpReferenceItemConnection!`](/api-reference/types/erp-reference-item-connection) # getAccountsByUserId Source: https://docs.fluz.app/api-reference/queries/get-accounts-by-user-id Get all the accounts information for the User using 'Basic '. Get all the accounts information for the User using 'Basic \'. ```graphql theme={null} query { getAccountsByUserId( userId: UUID! ): [Account] } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[Account]`](/api-reference/types/account) — Represents the account operable by the application. # getApplicationScopes Source: https://docs.fluz.app/api-reference/queries/get-application-scopes Get the allowed scopes of the application using 'Basic '. Get the allowed scopes of the application using 'Basic \'. ```graphql theme={null} query { getApplicationScopes: [ScopeType] } ``` ## Returns [`[ScopeType]`](/api-reference/types/scope-type) — Enum describing the various types of access scopes available within the system. # getApplicationUsers Source: https://docs.fluz.app/api-reference/queries/get-application-users Get the users who have granted scopes to the application using 'Basic '. Get the users who have granted scopes to the application using 'Basic \'. ```graphql theme={null} query { getApplicationUsers( paginate: OffsetInput ): [ApplicationUser] } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[ApplicationUser]`](/api-reference/types/application-user) — Represents the User who grants access to the application with scopes. # getBulkBalances Source: https://docs.fluz.app/api-reference/queries/get-bulk-balances Get cash balances across up to 100 connected users in one call, using 'Basic '. Get cash balances across up to 100 connected users in one call, using 'Basic \'. Requires the bulk API capability on your application, and the LIST\_PAYMENT scope on each target user's grant. Failures are reported per target and never fail the whole request. ```graphql theme={null} query { getBulkBalances( targetSpec: BulkTargetSpecInput! ): BulkBalances } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`BulkBalances`](/api-reference/types/bulk-balances) — Balances across your connected users, one result per target. # getBulkConnectedOAuthUsers Source: https://docs.fluz.app/api-reference/queries/get-bulk-connected-oauth-users List the users connected to your application through active OAuth grants, using 'Basic '. List the users connected to your application through active OAuth grants, using 'Basic \'. Requires the bulk API capability on your application. Returns identifiers and granted scopes only — never tokens. ```graphql theme={null} query { getBulkConnectedOAuthUsers( paginate: BulkPaginationInput ): BulkConnectedOAuthUsers } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`BulkConnectedOAuthUsers`](/api-reference/types/bulk-connected-oauth-users) — A page of users connected to your application. # getBulkTransactions Source: https://docs.fluz.app/api-reference/queries/get-bulk-transactions Get recent transactions across up to 100 connected users in one call (at most 20 per target), using 'Basic '. Get recent transactions across up to 100 connected users in one call (at most 20 per target), using 'Basic \'. Requires the bulk API capability on your application, and the LIST\_PAYMENT and LIST\_PURCHASES scopes on each target user's grant. Defaults to the last 90 days when no date filter is supplied. Failures are reported per target and never fail the whole request. For complete history use the asynchronous transactions export. ```graphql theme={null} query { getBulkTransactions( targetSpec: BulkTargetSpecInput! createdGte: DateTime createdLte: DateTime includeMetadata: Boolean ): BulkTransactions } ``` ## Arguments *No description provided in the schema yet.* Only include transactions created at or after this time. Defaults to 90 days before createdLte. Only include transactions created at or before this time. Defaults to now. Also return memo and category metadata for each transaction. Slower; defaults to false. ## Returns [`BulkTransactions`](/api-reference/types/bulk-transactions) — Recent transactions across your connected users, one result per target. # getBusinessCategories Source: https://docs.fluz.app/api-reference/queries/get-business-categories Query for listing all active business categories with their sub-categories. Query for listing all active business categories with their sub-categories. ```graphql theme={null} query { getBusinessCategories: [BusinessCategory!]! } ``` ## Returns [`[BusinessCategory!]!`](/api-reference/types/business-category) — BusinessCategory represents a business category with its associated sub-categories. # getCardProvisioningUrl Source: https://docs.fluz.app/api-reference/queries/get-card-provisioning-url Returns a short URL that, when opened on the end-user's mobile device, launches the Fluz App Clip (iOS) or Fluz app (Android) and adds the specified virtual card to Apple Pay / Google Pay. Returns a short URL that, when opened on the end-user's mobile device, launches the Fluz App Clip (iOS) or Fluz app (Android) and adds the specified virtual card to Apple Pay / Google Pay. Typical flow: 1. Developer creates a virtual card for a user via `createVirtualCard` and keeps the returned `virtualCardId`. 2. Developer calls `getCardProvisioningUrl` with that `virtualCardId`. 3. Developer surfaces the returned URL to the user (QR code, SMS, email, in-app button — anything that ends up on the user's mobile device). 4. User opens the URL within \`expiresAt\`. The App Clip / app handles the wallet provisioning. Each URL targets exactly one card. To provision several cards, call this once per card with each card's `virtualCardId`. `offerId` remains supported for the single-card case. It resolves only while the account holds at most one active card on that offer; beyond that it cannot identify a card and the query fails rather than picking one. Each call returns a fresh URL. The URL is opaque — it carries no credentials in plaintext; user identity is exchanged server-side via a short-lived lookup record (5-minute TTL). Treat the URL as a bearer secret: claiming it does not immediately invalidate it, so it stays redeemable for a short grace window after first use. Deliver it over a private channel and don't log or cache it. Requires the \`CREATE\_VIRTUALCARD\` scope. Possible errors: * `Arguments.INVALID` — id is not a valid UUID, or both/neither selector was supplied. * `VirtualCard.CARD_NOT_FOUND` — no such card on the caller's account. * `VirtualCard.CARD_NOT_ACTIVE` — the card exists but is not ACTIVE. * `VirtualCard.AMBIGUOUS_CARD_SELECTION` — offerId matches several active cards; pass virtualCardId. * `VirtualCard.OFFER_NOT_FOUND` — the card's offer doesn't exist or is inactive. * `VirtualCard.OFFER_NOT_ACCESSIBLE` — account lacks campaign access for the card's offer. * \`VirtualCard.OFFER\_NOT\_TOKENIZATION\_ELIGIBLE\` — offer's card program does not support wallet provisioning. * \`Auth.USER\_REVOKED\_DEVELOPER\_ACCESS\` — user has revoked OAuth access for this developer application. * \`Auth.INVALID\_SCOPE\` — caller's token does not have CREATE\_VIRTUALCARD. * \`Generic.EXTERNAL\_SERVICE\_ERROR\` — transient downstream failure; retry is safe. ```graphql theme={null} query { getCardProvisioningUrl( input: GetCardProvisioningUrlInput! ): CardProvisioningUrl! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`CardProvisioningUrl!`](/api-reference/types/card-provisioning-url) # getDeclinedTransactions Source: https://docs.fluz.app/api-reference/queries/get-declined-transactions Retrieves paginated declined transaction history for the authenticated user's account. Retrieves paginated declined transaction history for the authenticated user's account. Supports comprehensive filtering and pagination. Requires LIST\_PAYMENT and LIST\_PURCHASES scope. ```graphql theme={null} query { getDeclinedTransactions( filter: DeclinedTransactionFilterInput paginate: OffsetInput ): DeclinedTransactionConnection! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`DeclinedTransactionConnection!`](/api-reference/types/declined-transaction-connection) — Paginated response for getDeclinedTransactions query. # getDefaultFundingSource Source: https://docs.fluz.app/api-reference/queries/get-default-funding-source getDefaultFundingSource returns the account's current primary (bank account) and backup (bank card) funding sources. getDefaultFundingSource returns the account's current primary (bank account) and backup (bank card) funding sources. Requires LIST\_PAYMENT scope. ```graphql theme={null} query { getDefaultFundingSource: DefaultFundingSource } ``` ## Returns [`DefaultFundingSource`](/api-reference/types/default-funding-source) — DefaultFundingSource represents an account's default funding source: the primary payment method (a bank account, charged first) and the backup payment method (a bank card, charged if the primary can't be charged). # getGiftCards Source: https://docs.fluz.app/api-reference/queries/get-gift-cards Retrieves the user's gift cards. Retrieves the user's gift cards. Requires LIST\_PURCHASES scope. ```graphql theme={null} query { getGiftCards( status: [GiftCardStatus] userCashBalanceId: UUID paginate: OffsetInput ): [GiftCard] } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`[GiftCard]`](/api-reference/types/gift-card) — GiftCard represents a record of a gift card purchased by a user. # getMccList Source: https://docs.fluz.app/api-reference/queries/get-mcc-list Query for listing merchant category codes. Query for listing merchant category codes. Requires MAKE\_DEPOSIT scope. ```graphql theme={null} query { getMccList( paginate: OffsetInput ): [MerchantCategoryCode] } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[MerchantCategoryCode]`](/api-reference/types/merchant-category-code) — MerchantCategoryCode represents the classifier for a business by the types of goods or services it provides. # Authorize.net Source: https://docs.fluz.app/authorize-net Email sales to request access to our BETA program of Payment Processing API connectors. # Braintree Source: https://docs.fluz.app/braintree Email sales to request access to our BETA program of Payment Processing API connectors. # Operate on your customers' accounts Source: https://docs.fluz.app/build-a-platform Register or connect your customers, get a token scoped to each of them, and run the exact same APIs you already use on your own account. ## Two token paths, one API `API credentials` → `Your token` You use API keys to mint a User Access Token for your account. `OAuth grant` → `Their token` Customers authorize your app, and Fluz returns a token scoped to their account. From there, `createVirtualCard` is `createVirtualCard` — no separate platform API to learn. ## Onboard and connect customers Configure your app, redirect URIs, and scopes in the dashboard. [Create an OAuth app →](/create-an-o-auth-app) New customers: register individuals or businesses with built-in KYC and KYB. Existing Fluz users: send them through the grant flow. [User registration →](/user-registration) · [KYC verification →](/user-kyc-verification) The customer authorizes your app; you exchange the code for a token scoped to their account. [Grant flow →](/client-facing-o-auth-grant-flow) · [Exchange the code →](/exchange-an-o-auth-authorization-code) Send the customer-scoped token instead of your own. Everything else is identical. [Refresh tokens →](/refresh-o-auth-access-token) ## What you can do on connected accounts Every capability applies as written when you use a customer-scoped token. ## Take your app live Anything you build against your **own** account needs nothing from us. Certifying a **public** app — one that operates on other verified Fluz accounts — requires a due diligence review first. Register and verify your customers, or connect existing Fluz users through the grant flow. [User registration →](/user-registration) · [KYC verification →](/user-kyc-verification) · [Business registration →](/business-registration) Implement the capabilities your app needs in staging using a customer-scoped token. Every capability behaves exactly as documented on your own account. Submit the form or forms below that apply to your program. Our team reviews the submission and approves your application. We certify your public app and release production credentials. [Deploying to production →](/deploying-to-production) ## Due diligence **Platform due diligence is only required to operate on other verified accounts. You do not need it to build an application on your own account for your own activity.** Specialized verticals are the exception — those programs are reviewed regardless of whose account you operate on. Required if you are embedding Fluz to operate on behalf of your customers. Complete the platform due diligence form → Gaming, prediction markets, sweepstakes, and money services businesses follow an enhanced review path before going live. See the process, the timeline, and the form for your vertical → Required if you will interact directly with cards we issue, or pass us the actual cards of your users. You must be PCI compliant. Form link coming soon. Required if cards will carry your brand instead of the standard Fluz design. Bank and network approval adds 6–8 weeks — see the specifications and the submission process → We need these completed before we can certify your public app to go live. We need these completed before we can certify your public app to go live. # OAuth Applications Overview Source: https://docs.fluz.app/build-a-platform/oauth-applications-overview How your application gets permission to act on a Fluz user's account — the credentials involved, the permission model, the token lifecycle, and which page to read next. ## Why OAuth exists here Your application can already do everything the Fluz API offers **on your own account**. You mint a token with your API key and go — see [Authentication](/concepts/authentication) and [API credentials](/get-started/api-credentials). An OAuth application is what you need when the account isn't yours. The moment you want to issue a card on a customer's wallet, pull from their linked bank account, read their transactions, or pay them out, you need that person's explicit permission — and you need it in a form Fluz can verify, scope, expire, and revoke. That's what an OAuth application is: **a registered identity for your software, plus a consent mechanism that turns a user's approval into a token your server can use.** Once you hold a customer-scoped token, the API is identical. `createVirtualCard` on your own token creates a card on your wallet; the same mutation on a customer token creates it on theirs. OAuth changes *whose account you're touching*, not what you can do. *** ## Do you need one? You're issuing cards, buying gift cards, or moving money **within your own Fluz account**: a disbursement engine, a bulk card run, an internal spend tool, an ERP sync. Use your application API key to call `generateUserAccessToken` directly. No OAuth app, no consent screen, no redirect. Start at [API credentials](/get-started/api-credentials). You're building a platform where **your users have their own Fluz accounts** and you act on their behalf. You need an OAuth application, and each user has to grant your app scopes once. Then you hold a refreshable, customer-scoped token. Start with [Create an OAuth App](/create-an-o-auth-app), then see [Build a platform](/build-a-platform). You're embedding a [Fluz Widget](/developers/widgets). A widget **is** an OAuth application — one that ships with a hosted front end for the consent step instead of making you build a redirect flow. The credentials, scopes, and token mechanics on this page all apply to it. See [Configure App Widget](/developers/configure-app-widget). *** ## Three sets of credentials, three different jobs The most common source of confusion in this section is that a Fluz application carries more than one credential pair, and they are not interchangeable. | Credential | Where it lives | What it's for | Ever leaves your server? | | :----------------------------------- | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | | **API Key** / **API Secret** | Overview tab of your app | Identifies your *application*. Mints tokens on your own account (`Authorization: Basic `) and signs widget pre-approved transaction tokens. | Never | | **Client ID** / **Client Secret** | Overview tab of your app | Identifies your app to the *authorization server*. Used in the authorize URL and to exchange or refresh codes (`Authorization: Basic base64(client_id:client_secret)`). | Client ID is public; secret never | | **Access Token** / **Refresh Token** | Returned per user, per grant | Acts on one specific user's account with one specific set of scopes. | Sent as `Authorization: Bearer ` | Every secret here mints authority. An `apiSecret` leak lets someone sign transactions as your platform; a `client_secret` leak lets someone exchange codes as your app. Keep both server-side, out of browser bundles, out of mobile binaries, out of source control. *** ## The permission model Fluz enforces permissions at two levels, and an application's effective access is the **intersection** of the two. Set on the **Permissions** tab of your app. This is the maximum your application may ever request, independent of any user. A scope you haven't enabled here is silently ignored if you put it in an authorize URL — the request won't error, the scope simply won't be granted. Some scopes are administered by Fluz rather than self-selected. `PCI_COMPLIANCE` is granted at the application level only, to developers who have demonstrated PCI DSS compliance, and cannot be requested when generating a token. Set by the end user on the consent screen. They see the scopes you requested — grouped under readable top-level headers rather than raw enum values — and approve them. Anything they decline is not granted. Validated at `generateUserAccessToken`, not at call time. Both grants must exist and be unexpired. A revoked or lapsed grant therefore surfaces as a **token generation failure**, not as a permission error midway through a flow — which is usually the first place to look when a previously working integration stops working. Scopes by capability: | Area | Scopes | | :--------------------- | :---------------------------------------------------------------------------------- | | Funding sources | `LIST_PAYMENT`, `MANAGE_PAYMENT` | | Deposits & withdrawals | `MAKE_DEPOSIT`, `MAKE_WITHDRAW` | | Gift cards | `LIST_OFFERS`, `PURCHASE_GIFTCARD`, `REVEAL_GIFTCARD`, `LIST_PURCHASES` | | Virtual cards | `CREATE_VIRTUALCARD`, `EDIT_VIRTUALCARD`, `REVEAL_VIRTUALCARD`, `CREATE_SHARE_LINK` | | Card data | `PCI_COMPLIANCE` (application-level, Fluz-administered) | Use `getApplicationScopes` to read what's currently granted. Full reference: [Application Scopes](/fluz-dashboard/application-scopes). **Ask for less.** A shorter consent screen converts better, and a narrower token limits the damage if it leaks. Request what the flow in front of you needs and mint a new token when you need more. *** ## The lifecycle, end to end Each step below is a deep-dive page in this section. This is the map; the pages are the territory. From the developer dashboard, choose **Browse templates** and add the **OAuth Integration** template. Name it, subtitle it, describe it — those three fields are what your users will see on the consent screen, so write them for a human, not for your issue tracker. → [Create an OAuth App](/create-an-o-auth-app) On the **Permissions** tab, select your scope ceiling. On the **OAuth** tab, set your **Redirect URIs** (public, no query parameters, any number of them) and your **Webhook URLs** (each optionally subscribed to specific events; a URL with none selected becomes a catch-all). Add an avatar and logomark on **Overview** — the consent screen looks unfinished without them. → [Configure OAuth App](/configure-o-auth-app) Redirect to `/authorize` with `response_type=code`, your `client_id`, a registered `redirect_uri`, a space-delimited `scopes` list, and an optional `state` value you want handed back to you untouched. → [Client-facing OAuth grant flow](/client-facing-o-auth-grant-flow) On approval, Fluz redirects to your `redirect_uri` with `code` and your original `state`. On a misconfiguration, the redirect carries an error message describing what didn't match. Call `/token/exchange` with the `code` and **the exact same `redirect_uri`**, authenticated with `Authorization: Basic base64(client_id:client_secret)`. You get back an `accessToken`, a `refreshToken`, expiry timestamps, and the confirmed scope array. → [Exchanging an OAuth authorization code](/exchange-an-o-auth-authorization-code) Call `/token/refresh` with the `refresh_token` and the same Basic auth header. Access tokens are deliberately short-lived — on the order of ten minutes — while refresh tokens last roughly a month. Refresh silently in the background; only send a user back through consent when the refresh token itself has expired or the grant has been revoked. → [Refreshing an OAuth accessToken](/refresh-o-auth-access-token) Staging and production are separate environments with separate applications and separate credentials. Nothing carries over — you register the app, the redirect URIs, and the webhook endpoints again against production hosts. → [Deploying to Production](/deploying-to-production) *** ## Rules that bite Worth internalizing before you start, because each of these fails quietly or confusingly. The `redirect_uri` you send to `/authorize` must be registered on your app, and the one you send to `/token/exchange` must be byte-identical to the one you used at `/authorize`. Trailing slashes, `http` vs `https`, and host casing all count. Register no query parameters on the URI itself — use `state` to carry context instead. Request a scope you haven't checked on the Permissions tab and the authorize request still succeeds — the scope is dropped. Always read the `scope` array in the exchange response and treat it, not your request, as the truth about what you can do. Exchange it immediately, server-side, once. If your redirect handler can be replayed — a user refreshing the callback page, a link prefetcher — make sure a second attempt doesn't corrupt state. `Authorization: Basic `. Encode the joined string. Most integration failures at the exchange step are this. The redirect is a fresh browser navigation. If you need to know which user, which flow, or which page to return to, put a signed or server-looked-up reference in `state`. Don't put anything sensitive in it — it travels through the user's browser. If `generateUserAccessToken` starts failing for a user who worked yesterday, check whether the app-level grant or the user-level grant expired or was revoked, before you go looking at your code. *** ## OAuth apps vs. widgets Both are applications. Both use the permission model above. The difference is who builds the consent surface. | | OAuth application | Widget | | :---------------------------------------- | :---------------------------------------- | :-------------------------------------- | | Consent UI | You build the redirect flow | Fluz renders it in a modal on your page | | User leaves your site | Yes, to `/authorize` | No | | Sensitive data (PAN, SSN, documents, PIN) | You handle it, and you're in scope for it | Fluz collects and encrypts it | | Registration and KYC | Yours to build, or via API | Included in the flow | | Control over presentation | Complete | Limited to branding | | Time to first working flow | Days | Hours | You can mix them: register and KYC users over the API, then open a widget only for consent and sensitive capture. See [Embedded Widgets](/developers/widgets) for the hybrid patterns. *** ## Next steps Register your application from the OAuth Integration template. Scopes, redirect URIs, webhooks, branding. Build the authorize URL and handle the callback. Turn a code into an access token and refresh token. Stay authorized without re-prompting the user. Re-register against live hosts and go live. Building a platform where every one of your customers has a Fluz account? [Build a platform](/build-a-platform) walks the whole pattern end to end, and every capability on [API Features](/features) works identically on a connected account. # Bulk API Source: https://docs.fluz.app/bulk-api Read and act across many connected Fluz users in a single call, instead of looping over one user token at a time. The Bulk API lets your application act across **many connected Fluz users in a single call** — instead of looping over one user token at a time. It's built for OAuth application developers who need to read or (soon) move funds on behalf of the users who have connected to your app. This is different from the "Create Virtual Card Bulk Order" operation under Virtual Cards, which creates many cards for a *single* account. The Bulk API operates *across your connected users*. ## Who can use it The Bulk API is gated at the application level. Your app must have the **bulk API capability** enabled by Fluz — reach out to us to request access. Calls that aren't from a bulk-enabled application are rejected with `BulkApiAccessDenied`. ## Authentication All Bulk API calls use **Basic authentication with your application API key** (your `client_id` and `client_secret`), not a user access token: ``` Authorization: Basic ``` Endpoint (staging): ``` https://transactional-graph.staging.fluzapp.com/api/v1/graphql ``` Endpoint (production): ``` https://transactional-graph.fluzapp.com/api/v1/graphql ``` ## Authorization comes from existing connections The Bulk API never creates a new consent surface. A user is only reachable if they have an **active OAuth grant** with your application, and each operation is allowed only where the user has granted the scopes that operation requires. Bulk access is never broader than the equivalent single-user operation. * Disconnecting removes the grant, so a disconnected user simply stops being reachable. * If a user's grant is scoped to specific spend accounts, bulk results for that user are automatically restricted to those accounts. ## Selecting target users Every bulk operation takes a `targetSpec`: | Mode | Meaning | | --------------- | ------------------------------------------------------------------ | | `ALL_CONNECTED` | Every user currently connected to your application. | | `SELECTED` | Only the users whose `externalReferenceId`s you list in `targets`. | * Users are addressed by the `externalReferenceId` you supplied when they connected — not by `accountId`. * A synchronous request addresses at most **100 targets**. * Users connected without an `externalReferenceId` can't be selected individually; they're only reachable via `ALL_CONNECTED`. Use [Discover Connected Users](/discover-connected-users) to see who's connected and which scopes they granted. ## Per-target failure contract One target's failure **never fails the whole request**. Each result carries `success` and, when `success` is `false`, an `error`: | Code | Meaning | | --------------------------- | ------------------------------------------------------------------------------------------- | | `TARGET_NOT_CONNECTED` | The id isn't a user currently connected to your app (unknown, revoked, or never connected). | | `INVALID_TARGET_IDENTIFIER` | The id isn't a valid identifier. | | `INSUFFICIENT_SCOPE` | The user hasn't granted the scopes this operation requires. | | `ACCOUNT_NOT_PERMITTED` | The user's grant doesn't permit this operation on the requested account. | The whole request is only rejected for application-level problems: not bulk-enabled, an invalid request, exceeding the 100-target cap, or when *every* requested target is unresolvable. Each response also summarizes `targetCount`, `successCount`, and `failureCount`. ## Synchronous vs. asynchronous * **Bounded reads are synchronous** and served inline: up to 100 targets, with per-target limits (e.g. 20 transactions each, a 90-day window). These are the three queries documented in this section. * **Writes and large exports are asynchronous** (coming soon): you submit a job, poll its status, and download results. `hasNextPage`/`totalCount` on the sync reads tell you when to switch to an export rather than silently truncating. ## Operations in this section * [Discover Connected Users](/discover-connected-users) — `getBulkConnectedOAuthUsers` * [Get Bulk Balances](/get-bulk-balances) — `getBulkBalances` * [Get Bulk Transactions](/get-bulk-transactions) — `getBulkTransactions` # Bulk Gift Card Purchasing Source: https://docs.fluz.app/bulk-gift-card-purchasing A single `purchaseGiftCard` call buys **exactly one** gift card, for **one offer** at **one rate**. There is no `quantity` field, and a single call is never split across multiple offers or rates. To buy several cards, send the mutation multiple times — once per card, each with its own unique `idempotencyKey`. How each call chooses its offer and rate depends on whether you pass `offerId` or `merchantSlug`. ### Purchase flow at a glance ![](https://files.readme.io/bf6ad80159ab3e0beecb76d51f498a4093b793bf7368288abe62e5c81b2b36c5-diagram.svg) ### `offerId` — pin a specific offer and rate When you pass `offerId`, the purchase is locked to that exact offer and its rate. If that offer can no longer be fulfilled (for example, a stock-tracked offer that has sold out), the call **fails** — it will **not** silently substitute a different offer. See the out-of-stock error below. ### `merchantSlug` — auto-select the best available offer When you pass `merchantSlug` (without `offerId`), the system selects the **best available in-stock offer** for that merchant **at the moment of each call**. Because selection happens per call, repeated purchases for the same merchant can resolve to **different offers** as availability changes. ### Fixed vs. variable offers and stock * **Fixed-denomination offers** are stock-tracked per denomination. Once a denomination is depleted, it is no longer selectable, and any further calls pinned to it (via `offerId`) return an out-of-stock error. * **Variable offers** are not stock-limited in the same way. They typically remain available and act as the fallback when a fixed/stocked offer runs out. A variable offer often carries a **different (frequently lower) reward rate** than the fixed offer it replaces. > 📘 What happens when you buy more cards than are in stock > > Suppose a merchant's best offer is a fixed, stock-tracked offer with only **8** units left, and you want **10** cards (i.e. 10 separate `purchaseGiftCard` calls): > > * **Using** `offerId` (pinned to the fixed offer): the first 8 calls succeed; the 9th and 10th calls **fail** with an out-of-stock error. No automatic fallback to another offer or rate occurs. > * **Using** `merchantSlug` (auto-select): the first 8 calls purchase on the fixed offer; once it is depleted, the remaining calls auto-select the **next-best available offer** — which may be a **variable offer at a lower reward rate**. > > In every case each card is purchased atomically at the offer and rate resolved for that individual call — there is no blended or partially-fulfilled order. ### Protecting your rate with `minRewardRate` When you buy with `merchantSlug`, use `minRewardRate` to set a reward-rate floor. Before purchasing, the system checks the best available rate for the merchant, amount, and payment method; if that rate is **below your** `minRewardRate` (or no rate can be quoted), the call **fails** instead of buying at the lower rate. This is the recommended way to avoid unintentionally purchasing the remaining cards on a lower-rate variable offer after a higher-rate stocked offer sells out. > 🚧 `minRewardRate` only applies to `merchantSlug` purchases. > > If you provide `offerId`, `minRewardRate` is ignored (the offer — and its rate — is already fixed). The floor is evaluated per call, so include it on every call when buying multiple cards. ### Out-of-stock error When an offer can no longer be fulfilled due to stock, the mutation returns: ```json theme={null} { "code": "GC-0009", "message": "This offer is currently out of stock. Please select a different amount or try again later." } ``` Re-quote with `getOfferQuote` / `getMerchants` to find the current best available offer before retrying. > 📘 > > ### What happens when you buy more cards than are in stockSuppose a merchant's best offer is a fixed, stock-tracked offer with only **8** units left and you want **10** cards (10 separate `purchaseGiftCard` calls): > > * **Using** `offerId` (pinned to the fixed offer): the first 8 calls succeed at the stocked rate; the 9th and 10th calls **fail** with `GC-0009`. No automatic fallback to another offer or rate occurs. > * **Using** `merchantSlug` (auto-select): the first 8 calls purchase on the fixed offer at the higher rate; once it is depleted, the remaining calls auto-select the **next-best available offer** — which may be a **variable offer at a lower reward rate**. > 🚧 > > ### Protect your rate with `minRewardRate`en buying with `merchantSlug`, set `minRewardRate` to a reward-rate floor. Before each purchase the system checks the best available rate for the merchant, amount, and payment method; if that rate is **below** your `minRewardRate` (or no rate can be quoted), the call **fails** instead of buying at the lower rate. This is the recommended way to avoid unintentionally purchasing the remaining cards on a lower-rate variable offer after a higher-rate stocked offer sells out. > > `minRewardRate` is **ignored** when you provide `offerId` (the rate is already fixed), and it is evaluated **per call** — include it on every call when buying multiple cards. To check stock before you buy, see [Get Inventory On Stocked Offers](/get-inventory). # Business Categories Source: https://docs.fluz.app/business-categories ## Overview Retrieves all active business categories and their sub-categories from the API. Use this query to get the `businessCategoryId` and `businessSubCategoryId` values required for the **registerBusiness** mutation.
## Required scopes | Property | Value | | :-------------- | :------------------ | | Endpoint | GraphQL API | | Authentication | OAuth Bearer Token | | Required Scopes | `REGISTER_BUSINESS` |
## Parameters This query does not require any parameters and does not support pagination
## Basic query structure **Sample request:** ```graphql theme={null} query GetBusinessCategories { getBusinessCategories { businessCategoryId categoryName categoryDescription businessSubCategories { businessSubCategoryId subCategoryName } } } ``` **Sample response:** ```json json theme={null} { "data": { "getBusinessCategories": [ { "businessCategoryId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "categoryName": "Retail Trade", "categoryDescription": "Businesses engaged in selling merchandise to consumers", "businessSubCategories": [ { "businessSubCategoryId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "subCategoryName": "E-commerce" }, { "businessSubCategoryId": "c3d4e5f6-a7b8-9012-cdef-123456789012", "subCategoryName": "Brick and Mortar Retail" } ] }, { "businessCategoryId": "d4e5f6a7-b8c9-0123-defg-234567890123", "categoryName": "Professional Services", "categoryDescription": "Businesses providing professional or consulting services", "businessSubCategories": [ { "businessSubCategoryId": "e5f6a7b8-c9d0-1234-efgh-345678901234", "subCategoryName": "Consulting" }, { "businessSubCategoryId": "f6a7b8c9-d0e1-2345-fghi-456789012345", "subCategoryName": "Legal Services" } ] } ] } } ```
## Response details | Field | Type | Description | | ----------------------- | ----------------------- | -------------------------------------------------- | | `businessCategoryId` | `UUID` | Unique identifier for the business category | | `categoryName` | `String` | Name of the business category | | `categoryDescription` | `String` | Description of the business category (may be null) | | `businessSubCategories` | `[BusinessSubCategory]` | List of sub-categories within this category |
### BusinessSubCategory | Field | Type | Description | | ----------------------- | -------- | ----------------------------------------------- | | `businessSubCategoryId` | `UUID` | Unique identifier for the business sub-category | | `subCategoryName` | `String` | Name of the sub-category |
## cURL Example ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "query GetBusinessCategories { getBusinessCategories { businessCategoryId categoryName categoryDescription businessSubCategories { businessSubCategoryId subCategoryName } } }" }' ```
*** ## Notes * Categories are sorted alphabetically by `categoryName`. * Use the `businessCategoryId` and `businessSubCategoryId` values from this query when calling the `registerBusiness` mutation. * The subcategory must be part of the chosen category to be valid during business registration. ***
# Register & Verify Businesses Source: https://docs.fluz.app/business-registration Create a business account programmatically, submit beneficial ownership information, and take the account through KYB verification. ## Overview The **registerBusiness** mutation creates a business account on the Fluz platform. In a single call it: 1. Creates a business account tied to an existing Fluz user (the primary owner), 2. Stores the legal entity record — legal name, structure, tax ID, state of incorporation, legal address, category, and intended use of the account, 3. Stores the beneficial ownership information for each owner you supply, and 4. Opens a **KYB (Know Your Business)** case for compliance review. The mutation returns an `accountId` immediately, along with a `kybStatus` of `PENDING`. Registration succeeding means Fluz accepted and validated your submission — it does **not** mean the business has been approved. Approval happens asynchronously once the KYB review completes. **Registration is validation, not approval.** A `success` response with `kybStatus: PENDING` confirms the payload passed field-level validation and a KYB case was opened. Build your integration so it waits for an approved status before attempting to fund the account or issue cards. ### What a business account unlocks Once KYB is approved, the business account can be used for the commercial side of the platform: * Business spend accounts and balances * Commercial virtual cards, including bulk issuance * Authorized users and card-level spend controls * Approval workflows for cards, transfers, and reimbursements * Business-level transaction reporting and expense annotation ### When to use this endpoint Use `registerBusiness` when your platform onboards businesses on Fluz rails and you want to collect entity and ownership data in your own UI rather than sending users into a Fluz-hosted flow. If you would rather Fluz host the collection and document upload experience, talk to your account manager about the widget-based onboarding option instead. *** ## Registration flow ### End-to-end sequence ```mermaid theme={null} sequenceDiagram autonumber participant App as Your Application participant API as Fluz GraphQL API participant Files as Fluz File Upload (REST) participant KYB as Fluz Compliance / KYB Note over App,API: Step 0 — the primary owner must exist as a Fluz user App->>API: registerUser (only if the primary owner is new) API-->>App: success Note over App,API: Step 1 — resolve category IDs App->>API: query getBusinessCategories API-->>App: businessCategoryId + businessSubCategoryId values opt businessStructure = SOLE_PROPRIETORSHIP App->>Files: Upload document Files-->>App: document URL end Note over App,API: Step 2 — submit the business App->>API: mutation registerBusiness(input) alt Validation fails API-->>App: success false, with error code and message App->>App: Correct the field and resubmit else Validation passes API-->>App: accountId plus kybStatus PENDING API->>KYB: Open KYB case end Note over KYB,App: Step 3 — asynchronous review KYB->>KYB: Entity, tax ID, address, and owner checks KYB-->>API: Decision (or request for more documentation) App->>API: Check business account status API-->>App: Updated KYB status Note over App,API: Step 4 — go live App->>API: Create spend accounts and issue cards ``` ### Step-by-step At least one owner in the `owners` array — the primary owner — must already exist as a Fluz user, and the `emailAddress` you send for that owner must match the email on their Fluz account exactly. If the person does not have an account yet, create one first with [registerUser](/user-registration). Fluz recommends the primary owner complete [identity verification (KYC)](/docs/user-kyc-verification) before or alongside the business submission, since owner identity data is reviewed as part of KYB. Both the app-level grant and the individual user grant must be active. See [Application Scopes](/fluz-dashboard/application-scopes). Call [getBusinessCategories](/business-categories) and let the user pick a category and one of that category's sub-categories. Do not hardcode these UUIDs — they can change, and a sub-category from a different category will be rejected with `BS-0006`. Only required when `businessStructure` is `SOLE_PROPRIETORSHIP`. Upload the document first, then pass the returned URL in `soleProprietorshipDocumentUrl`. See [Submit business documents](/submit-business-documents). Send the full entity record and every owner in one call. Handle the response as described in [Response details](#response-details) — errors are returned inside the payload, not as GraphQL errors. The account is created in `PENDING`. Surface that state to your user rather than implying they are live. Once the status moves to approved, create spend accounts and issue cards. ### KYB status lifecycle ```mermaid theme={null} stateDiagram-v2 [*] --> PENDING: registerBusiness returns accountId PENDING --> PENDING: Additional documentation requested PENDING --> APPROVED: Entity and ownership checks cleared PENDING --> DECLINED: Checks not cleared APPROVED --> [*]: Business can transact DECLINED --> [*]: New submission required ``` | Status | What it means | What your app should do | | ---------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------- | | `PENDING` | The submission was accepted and is under review. | Show an "under review" state. Do not attempt to fund the account or issue cards. | | `APPROVED` | KYB cleared. The business account is usable. | Provision spend accounts, issue cards, and unlock your business UI. | | `DECLINED` | KYB did not clear. | Surface a neutral message and direct the user to support. Do not auto-retry the submission. | ### Checking status after registration There is no KYB webhook event today, so your integration should read the status when the user returns to your business onboarding screen, and on a low-frequency background schedule (for example, hourly — not per page load). You can confirm the business account exists and identify it against the user with `getAccountsByUserId`: ```graphql theme={null} query GetAccountsByUserId($userId: UUID!) { getAccountsByUserId(userId: $userId) { accountId type accountName } } ``` Reviews are typically resolved within one to two business days, but can take longer when additional documentation is requested. If a case appears stalled, contact your account manager with the `accountId` rather than resubmitting — a second submission will be blocked by `BS-0007`. *** ## Required scopes | Property | Value | | --------------- | ------------------- | | Endpoint | GraphQL API | | Authentication | OAuth Bearer Token | | Required Scopes | `REGISTER_BUSINESS` | ## Basic mutation structure ```graphql theme={null} mutation RegisterBusiness($input: RegisterBusinessInput!) { registerBusiness(input: $input) { accountId kybStatus success error { message code } } } ``` ## Parameters | Parameter | Type | Required | Description | | ------------------------------- | ------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------- | | `businessName` | `String` | Yes | The legal business name, exactly as it appears on the entity's formation documents and tax filings | | `dbaName` | `String` | No | The "Doing Business As" name | | `businessStructure` | `BusinessStructure` | Yes | Must be one of: `LLC`, `CORPORATION`, `PARTNERSHIP`, `SOLE_PROPRIETORSHIP`, `COOP` | | `businessLegalAddress` | `BusinessLegalAddress` | Yes | The business legal address object (see below) | | `stateOfIncorporation` | `String` | Yes | The state where the business is incorporated | | `taxId` | `String` | Yes | Tax ID / EIN Number. Format: `XX-XXXXXXX` (2 digits, hyphen, 7 digits) | | `soleProprietorshipDocumentUrl` | `String` | Conditional | Required if `businessStructure` is `SOLE_PROPRIETORSHIP`. [Upload the document](/submit-business-documents) first | | `businessCategoryId` | `UUID` | Yes | Business category ID (from **getBusinessCategories** query) | | `businessSubCategoryId` | `UUID` | Yes | Business sub-category ID (from **getBusinessCategories** query) | | `natureOfBusiness` | `String` | No | Brief description of the nature of business. Strongly recommended — it speeds up manual review | | `websiteUrl` | `String` | No | The business website URL. Strongly recommended for online sellers | | `businessAccountUsage` | `[BusinessAccountUsage]` | Conditional | Array of usage types. Required if `businessAccountUsageOther` is not provided | | `businessAccountUsageOther` | `String` | Conditional | Required if `businessAccountUsage` is empty or not provided | | `owners` | `[BusinessOwner]` | Yes | List of business owners. At least one owner required. Primary owner must be a registered Fluz user | **Address formatting** Format `businessLegalAddress` and each owner `address` using the structured fields below, with a real, deliverable address and a consistent city / state / postal code. The **business legal address** may be international (country name, ISO 3166; some countries are restricted, e.g. Russia or Iran). **Owner addresses must be US-based.** A malformed or mismatched address returns `BS-0002` (business legal address) or `BS-0003` (owner information). See [Address Formatting Requirements](/concepts/address-formatting-requirements) for details. ### BusinessLegalAddress | Field | Type | Required | Description | | -------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------ | | `streetAddressLine1` | `String` | Yes | Street address line 1 | | `streetAddressLine2` | `String` | No | Street address line 2 | | `city` | `String` | Yes | City | | `state` | `String` | Yes | State/province | | `postalCode` | `String` | Yes | Postal code | | `country` | `String` | Yes | Country name (ISO 3166). Some countries are restricted for KYB registration, e.g. Russia or Iran | ### BusinessOwner | Field | Type | Required | Description | | --------------------- | -------------- | -------- | ---------------------------------------------------------------------------------------------- | | `firstName` | `String` | Yes | Owner's first name | | `lastName` | `String` | Yes | Owner's last name | | `emailAddress` | `String` | Yes | Owner's email. Ensure you use the email address the user registered with on the Fluz platform. | | `title` | `String` | Yes | Owner's title in the company | | `ownershipPercentage` | `Int` | Yes | Percentage of ownership (0-100). Total ownership across all owners cannot exceed 100 | | `lastFourSsnDigits` | `String` | Yes | Last 4 digits of SSN | | `dob` | `String` | Yes | Date of birth in `MM/DD/YYYY` format (e.g., `02/28/1975`) | | `phoneNumber` | `String` | Yes | Phone number: minimum 10 digits, digits only | | `address` | `OwnerAddress` | Yes | Owner's address (must be US-based, see `OwnerAddress`) | ### OwnerAddress | Field | Type | Required | Description | | -------------------- | -------- | -------- | ----------------------------------------------------------------------- | | `streetAddressLine1` | `String` | Yes | Street address line 1 | | `streetAddressLine2` | `String` | No | Street address line 2 | | `city` | `String` | Yes | City | | `state` | `String` | Yes | Must be a valid US state name (e.g., `California`, `New York`, `Texas`) | | `postalCode` | `String` | Yes | Must be 5 digits for US ZIP code format | ### BusinessStructure (enum) | Value | Notes | | --------------------- | ---------------------------------------- | | `LLC` | Single- or multi-member | | `CORPORATION` | C-corp or S-corp | | `PARTNERSHIP` | General or limited | | `SOLE_PROPRIETORSHIP` | Requires `soleProprietorshipDocumentUrl` | | `COOP` | Cooperative | Any structure not on this list is rejected with `BS-0005`. Trusts, non-profits, and other entity types are handled case by case — contact your account manager before building against them. ### BusinessAccountUsage (enum) | Value | Use for | | --------------------------------- | --------------------------------------------------------------- | | `CORPORATE_GIFTING` | Buying gift cards for employees, clients, or incentive programs | | `CORPORATE_SPENDING_ADMIN` | Managing employee cards and controlled spend | | `REWARDS_MAXIMIZER` | Optimizing rewards on business spend | | `GIFT_CARD_RESELLING` | Reselling gift cards | | `PURCHASE_GOODS_FOR_BUSINESS_USE` | General procurement of goods and services | | `ONLINE_SELLER_RETAIL_PURCHASING` | Sourcing inventory for online retail | Send every value that applies. If none fit, omit `businessAccountUsage` and describe the intended use in `businessAccountUsageOther`. Selecting a usage type your application is not approved for returns `BS-0004`. *** ## Beneficial ownership requirements KYB review depends on getting the ownership picture right the first time. Collect and submit: * **Every individual who owns 25% or more** of the entity, directly or indirectly. * **A control person** — an individual with significant responsibility for managing the entity (CEO, CFO, managing member, general partner, or similar) — even if they hold no equity. Send them with an `ownershipPercentage` of `0` and an accurate `title`. * **At least one owner who is a registered Fluz user**, with a matching `emailAddress`. Practical notes: * Total `ownershipPercentage` across the array must not exceed 100, but it does **not** need to equal 100. If a business is 40/35/25 across three individuals plus a non-owner CEO, submit all four with percentages of 40, 35, 25, and 0. * Where an entity (rather than a person) holds equity, look through to the individuals behind it and submit those individuals. * `title` is a free-text field, but it is read by a human reviewer. Use recognizable titles ("Chief Executive Officer", "Managing Member") rather than internal shorthand. ## Validation quick reference Most `BS-000x` errors come down to formatting. Check these before submitting: | Field | Rule | | ---------------------------------------- | ----------------------------------------------------------------------- | | `taxId` | `XX-XXXXXXX` — 2 digits, hyphen, 7 digits. Strip any other punctuation. | | `owners[].dob` | `MM/DD/YYYY` | | `owners[].phoneNumber` | Digits only, minimum 10. No `+`, spaces, parentheses, or hyphens. | | `owners[].lastFourSsnDigits` | Exactly 4 digits, as a string | | `owners[].ownershipPercentage` | Integer 0–100; sum across owners ≤ 100 | | `owners[].address.state` | Full US state name, not the two-letter abbreviation | | `owners[].address.postalCode` | 5 digits | | `businessLegalAddress.country` | ISO 3166 country name; restricted countries are rejected | | `businessSubCategoryId` | Must belong to the supplied `businessCategoryId` | | `businessAccountUsage` / `...UsageOther` | Exactly one of the two must be populated | | `soleProprietorshipDocumentUrl` | Required when `businessStructure` is `SOLE_PROPRIETORSHIP` | **Date format differs from user registration.** `registerBusiness` expects owner dates of birth as `MM/DD/YYYY`, while [registerUser](/user-registration) expects `YYYY-MM-DD`. Reusing one formatter across both calls is a common source of `BS-0003`. ## Documents Sole proprietorships must upload supporting documentation before registering, and any structure may be asked for additional documentation during KYB review. Both paths are covered on one page: Upload endpoint, accepted sole proprietorship documents, and what to do when compliance requests more information. ## Response details | Field | Type | Description | | ----------- | ----------------------- | ------------------------------------------------------- | | `accountId` | `UUID` | The account ID of the newly created business | | `kybStatus` | `String` | The KYB status of the business (initially `PENDING`) | | `success` | `Boolean` | Indicates if the registration was successful (on error) | | `error` | `RegisterBusinessError` | Error information if registration failed (on error) | ### RegisterBusinessError | Field | Type | Description | | --------- | -------- | ----------------------------------------- | | `message` | `String` | Brief informational error message | | `code` | `String` | Predefined code listed in the table below | Failures are returned **inside the response payload**, not as top-level GraphQL errors. Always branch on the presence of `error` (or on `success === false`) rather than relying on the HTTP status or a GraphQL `errors` array. ## cURL Example ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "query": "mutation RegisterBusiness($input: RegisterBusinessInput!) { registerBusiness(input: $input) { accountId kybStatus success error { message code } } }", "variables": { "input": { "businessName": "Acme Corporation", "dbaName": "Acme Co", "businessStructure": "LLC", "businessLegalAddress": { "streetAddressLine1": "123 Main Street", "streetAddressLine2": "Suite 100", "city": "San Francisco", "state": "California", "postalCode": "94102", "country": "United States" }, "stateOfIncorporation": "California", "taxId": "12-3456789", "businessCategoryId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "businessSubCategoryId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "natureOfBusiness": "E-commerce retail", "websiteUrl": "https://acme.example.com", "businessAccountUsage": ["CORPORATE_SPENDING_ADMIN", "PURCHASE_GOODS_FOR_BUSINESS_USE"], "owners": [ { "firstName": "John", "lastName": "Doe", "emailAddress": "john@example.com", "title": "CEO", "ownershipPercentage": 60, "lastFourSsnDigits": "1234", "dob": "02/28/1975", "phoneNumber": "4155551234", "address": { "streetAddressLine1": "456 Oak Avenue", "city": "San Francisco", "state": "California", "postalCode": "94103" } }, { "firstName": "Jane", "lastName": "Smith", "emailAddress": "jane@example.com", "title": "CFO", "ownershipPercentage": 40, "lastFourSsnDigits": "5678", "dob": "07/15/1980", "phoneNumber": "4155555678", "address": { "streetAddressLine1": "789 Pine Street", "city": "San Francisco", "state": "California", "postalCode": "94104" } } ] } } }' ``` ## Example Response ### Success ```json theme={null} { "data": { "registerBusiness": { "accountId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "kybStatus": "PENDING" } } } ``` ### Error ```json theme={null} { "data": { "registerBusiness": { "success": false, "error": { "message": "The entered tax id taxId must be in format XX-XXXXXXX (2 digits, hyphen, 7 digits)", "code": "BS-0001" } } } } ``` ## Error Codes | Code | Name | Description | How to resolve | | --------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `BS-0001` | InvalidTaxId | The tax ID must be in format `XX-XXXXXXX` (2 digits, hyphen, 7 digits) | Reformat the EIN. Strip spaces and any punctuation other than the single hyphen. | | `BS-0002` | InvalidBusinessLegalAddress | Business legal address is incorrect | Validate against [Address Formatting Requirements](/concepts/address-formatting-requirements). | | `BS-0003` | InvalidOwnerInformation | One or more owner information is incorrect | Check `dob` format, digits-only `phoneNumber`, 4-digit SSN, full state name, and 5-digit ZIP. | | `BS-0004` | InvalidBusinessAccountUsage | Selected business account usage is not allowed | Confirm your app is approved for that usage type, or describe it in `businessAccountUsageOther`. | | `BS-0005` | InvalidBusinessStructure | Selected business structure is not allowed | Use one of the five supported enum values, or contact your account manager for other entity types. | | `BS-0006` | InvalidBusinessCategory | Ensure that business category and business subcategory are valid and properly connected (subcategories from different categories cannot be combined; for example, Art cannot be selected with a Pharmacy subcategory) | Re-fetch [getBusinessCategories](/business-categories) and pass a sub-category from the same category. | | `BS-0007` | RegistrationNotAllowed | Wait until the last business finishes registration before starting a new one | The user has an open application. Wait for approval or decline before resubmitting. | | `AR-0001` | MissingArguments | Required argument is missing (see error message for details) | Read `message` for the field name. | | `AR-0002` | InvalidArguments | Invalid argument provided (see error message for details) | Read `message` for the field name. | | `AU-0001` | UnsuccessfulRegistration | General registration failure | Retry once. If it persists, contact support with the request payload and timestamp. | ## Testing in staging * Register against the staging GraphQL endpoint shown in the examples above. See [Staging vs. Live Environment](/docs/staging-vs-live-environment). * Use [Test Addresses](/docs/test-addresses) for addresses that pass validation deterministically. * EINs in staging must still satisfy the `XX-XXXXXXX` format, but do not need to correspond to a real entity. * Because a user cannot hold two open applications (`BS-0007`), test repeated registration paths with distinct test users. ## Best practices * **Validate client-side first.** Every `BS-000x` code except `BS-0007` is a formatting or selection problem you can catch before the network call. Doing so materially improves onboarding completion rates. * **Fetch categories at runtime.** Never hardcode category UUIDs. * **Do not auto-retry on a declined KYB.** Resubmission will not change the outcome and creates duplicate cases. * **Store the `accountId` immediately.** It is your only handle on the application and the reference support will ask for. * **Communicate the pending state honestly.** Tell the user their business is under review and roughly how long it takes, rather than dropping them into a business dashboard that cannot yet transact. * **Collect ownership completely the first time.** Missing beneficial owners is the most common cause of a review stalling for additional documentation. ## Notes * When specifying account usage, either `businessAccountUsage` or `businessAccountUsageOther` must be provided. * Users cannot register a new business if they already have an ongoing application. They must wait for the current application to be approved or rejected before submitting another one. ## Related pages Upload sole proprietorship documents and respond to KYB documentation requests. Fetch the category and sub-category IDs required by this mutation. Create the Fluz user who will act as primary owner. Rules that govern the legal and owner address objects. # Cancel a Bulk Operation Source: https://docs.fluz.app/cancel-a-bulk-operation > Stop a bulk job that hasn't finished. Items not yet started are cancelled; items already running are allowed to finish. `cancelBulkOperation` stops a job you [submitted](/submit-a-bulk-operation). It is **best-effort and irreversible**: items still `QUEUED` are cancelled, while items already `RUNNING` are allowed to finish and record their results. It does not roll back work that has already completed. Available in `staging` only at this point. ## Requirements * `Authorization: Basic ` * The bulk API capability on your application. * The job must belong to your application — an unknown or other-app job id returns not-found. ## Mutation ```graphql theme={null} mutation CancelBulkOperation($bulkJobId: UUID!) { cancelBulkOperation(bulkJobId: $bulkJobId) { jobId status succeededItemCount failedItemCount skippedItemCount } } ``` ```json theme={null} { "bulkJobId": "9c1e6f2a-1d4b-4a2e-8f0c-2b7e5a9d1234" } ``` ## Response ```json theme={null} { "data": { "cancelBulkOperation": { "jobId": "9c1e6f2a-1d4b-4a2e-8f0c-2b7e5a9d1234", "status": "CANCELLED", "succeededItemCount": 4, "failedItemCount": 0, "skippedItemCount": 0 } } } ``` ## Arguments | Parameter | Type | Description | | ----------- | ------- | --------------------------------------------- | | `bulkJobId` | `UUID!` | The job id returned by `submitBulkOperation`. | ## Response fields | Field | Description | | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `status` | `CANCELLED` once the job has stopped accepting new work. Items that had already succeeded or failed keep their outcome. | | `succeededItemCount` / `failedItemCount` / `skippedItemCount` | Final per-item counters, including any items that finished after the cancel call landed. | ## Errors | Error | HTTP | When | | --------------------- | ---- | --------------------------------------------------------------------------------------------- | | `BulkApiAccessDenied` | 403 | Your application does not have the bulk API capability. | | `BulkJobNotFound` | 404 | The job id is unknown **or** belongs to another application (deliberately indistinguishable). | Cancelling an already-finished or already-cancelled job is **not** an error — it returns the job unchanged (see below). Cancel is **idempotent and safe to retry**. Calling it on a job that is already finished (`COMPLETED`, `COMPLETED_WITH_ERRORS`, `FAILED`) or already `CANCELLED` returns the job unchanged — it never errors and never reverses completed items. Because in-flight items are allowed to finish, poll the job with [Track a Bulk Job](/track-a-bulk-job) if you need the settled counts after cancelling. # Checkout Source: https://docs.fluz.app/checkout Email sales to request access to our BETA program of Payment Processing API connectors. # Client-Facing OAuth Grant Flow Source: https://docs.fluz.app/client-facing-o-auth-grant-flow Send a user to Fluz to approve your scopes, and handle the authorization code that comes back — including the state validation that makes the flow safe. This is the step where a real person decides whether to let your application touch their Fluz account. You redirect them to Fluz, they approve, and Fluz redirects them back to you with a short-lived authorization code. Everything before this page is configuration. Everything after it is token handling. This is the only step your user sees. ## Where this sits ```mermaid theme={null} sequenceDiagram participant U as User participant Y as Your server participant F as Fluz U->>Y: Clicks "Connect Fluz" Y->>Y: Generate and store `state` Y->>U: 302 to /authorize U->>F: Loads consent screen F->>U: Sign in, 2FA, review scopes U->>F: Approves F->>U: 302 to your redirect_uri (code, state) U->>Y: Hits your callback Y->>Y: Validate `state` Y->>F: POST /token/exchange (code) F->>Y: accessToken, refreshToken, scope ``` Note what Fluz absorbs in the middle of that diagram: account creation, sign-in, two-factor authentication, and identity verification if the user hasn't been verified yet. You don't build any of it, and you never see the credentials. ## Before you start Scope ceiling set on the **Permissions** tab, redirect URI registered on the **OAuth** tab, `client_id` and `client_secret` in your secret store. See [Configure OAuth App](/configure-o-auth-app). It needs to read `state` and compare it against something you persisted before the redirect. See [Authentication](/concepts/authentication) for the full list. Request the minimum — the consent screen is your highest-drop-off step and its length comes from this list. *** ## Step 1 — Build the authorize URL Direct the user to the `/authorize` endpoint with the following query parameters. | Parameter | Required | Description | | :-------------- | :---------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | `response_type` | Yes | Always `code` for a permissions request. | | `client_id` | Yes | Your app's OAuth client ID, from the **Overview** tab. | | `redirect_uri` | Yes | A URI already registered on your app. Must be reused byte-for-byte at exchange. | | `scopes` | Yes | Space-delimited list of scopes, URL-encoded. Must be a subset of your app's ceiling. | | `state` | Technically optional — **treat it as required** | An unguessable value returned to you unmodified. See [Step 2](#step-2-%E2%80%94-protect-the-flow-with-state). | The parameter is **`scopes`**, plural — not `scope` as in the base OAuth 2.0 specification. If you're using a generic OAuth client library, this is the field you'll have to override. ### Environments | Environment | Authorize endpoint | | :---------- | :-------------------------------------------------------------------------------------------- | | Staging | `https://uni.staging.fluzapp.com/authorize` | | Production | Issued during production onboarding — see [Deploying to Production](/deploying-to-production) | ### Encoding rules Spaces between scopes must be encoded as `%20`. URL-encode the `redirect_uri` value as well; build the query string with your language's URL encoder rather than string concatenation, and these take care of themselves. A complete staging example: ```text theme={null} https://uni.staging.fluzapp.com/authorize?response_type=code&client_id=dab5c80e-0321-4c3a-988a-ffedfd64d8db&redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Ffinalize&scopes=MAKE_DEPOSIT%20LIST_PAYMENT%20MAKE_WITHDRAW%20REVEAL_VIRTUALCARD&state=8f14e45fceea167a5a36dedd4bea2543 ``` ```javascript Node.js theme={null} import crypto from 'crypto'; const AUTHORIZE_URL = 'https://uni.staging.fluzapp.com/authorize'; const REDIRECT_URI = 'https://app.example.com/oauth/finalize'; // one canonical constant export function buildAuthorizeUrl(session) { const state = crypto.randomBytes(32).toString('hex'); session.oauthState = state; // persist server-side, bound to this session const url = new URL(AUTHORIZE_URL); url.searchParams.set('response_type', 'code'); url.searchParams.set('client_id', process.env.FLUZ_CLIENT_ID); url.searchParams.set('redirect_uri', REDIRECT_URI); url.searchParams.set('scopes', ['MAKE_WITHDRAW', 'LIST_PAYMENT'].join(' ')); url.searchParams.set('state', state); return url.toString(); } ``` ```python Python theme={null} import os import secrets from urllib.parse import urlencode AUTHORIZE_URL = "https://uni.staging.fluzapp.com/authorize" REDIRECT_URI = "https://app.example.com/oauth/finalize" # one canonical constant def build_authorize_url(session): state = secrets.token_hex(32) session["oauth_state"] = state # persist server-side, bound to this session params = { "response_type": "code", "client_id": os.environ["FLUZ_CLIENT_ID"], "redirect_uri": REDIRECT_URI, "scopes": " ".join(["MAKE_WITHDRAW", "LIST_PAYMENT"]), "state": state, } return f"{AUTHORIZE_URL}?{urlencode(params)}" ``` ```go Go theme={null} package fluz import ( "crypto/rand" "encoding/hex" "net/url" "os" "strings" ) const ( authorizeURL = "https://uni.staging.fluzapp.com/authorize" redirectURI = "https://app.example.com/oauth/finalize" ) func BuildAuthorizeURL() (authURL string, state string, err error) { b := make([]byte, 32) if _, err = rand.Read(b); err != nil { return "", "", err } state = hex.EncodeToString(b) // persist server-side, bound to this session u, err := url.Parse(authorizeURL) if err != nil { return "", "", err } q := u.Query() q.Set("response_type", "code") q.Set("client_id", os.Getenv("FLUZ_CLIENT_ID")) q.Set("redirect_uri", redirectURI) q.Set("scopes", strings.Join([]string{"MAKE_WITHDRAW", "LIST_PAYMENT"}, " ")) q.Set("state", state) u.RawQuery = q.Encode() return u.String(), state, nil } ``` ### What the user sees ![Sample OAuth permissions page](https://storage.googleapis.com/fluz-fluz-file-uploads-staging-wlfprelricuyxowb/assets/oauth-widget-permissions.png) Your app name, avatar, and description come straight from the **Overview** tab, and the permission lines are your selected scopes grouped under readable headers. If this screen looks wrong, the fix is on [Configure OAuth App](/configure-o-auth-app), not in your code. *** ## Step 2 — Protect the flow with `state` The reference table calls `state` optional. In a redirect-based authorization flow it is your only defense against having someone else's authorization code planted in your user's session, so build it in from the first commit rather than adding it later. At least 128 bits from a cryptographically secure source. Not a timestamp, not a user ID, not a counter. Session store, signed cookie, or short-TTL cache keyed to the session. Not in a global. Missing, unrecognized, or already-used `state` means abandon the request — do not exchange the code. Use a constant-time comparison. Delete it after a successful match so the same callback can't be replayed. `state` travels through the user's browser. It's fine to use it to carry a lookup key — which user, which flow, which page to return to — but never put anything sensitive or trusted in the value itself. *** ## Step 3 — Handle the callback On approval, Fluz redirects the user to your `redirect_uri` with: | Parameter | Description | | :-------- | :-------------------------------------------------------------------------------------------------------- | | `code` | The authorization code tied to this user's scope grant. Single-use, short-lived. Exchange it server-side. | | `state` | The exact value you sent, unmodified. | If the request was misconfigured, the redirect carries an error message describing what didn't match. ```javascript Node.js theme={null} import crypto from 'crypto'; app.get('/oauth/finalize', async (req, res) => { const { code, state } = req.query; const expected = req.session.oauthState; delete req.session.oauthState; // single use, regardless of outcome if (!code || !state || !expected) { return res.status(400).send('Incomplete authorization response'); } const a = Buffer.from(String(state)); const b = Buffer.from(String(expected)); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) { return res.status(400).send('State mismatch — request abandoned'); } // Exchange server-side. Never send the code back to the browser. const tokens = await exchangeAuthorizationCode(code); await persistTokens(req.session.userId, tokens); // Trust the returned scope array, not what you requested. res.redirect('/settings/connections'); }); ``` ```python Python theme={null} import hmac from flask import request, session, abort, redirect @app.route("/oauth/finalize") def oauth_finalize(): code = request.args.get("code") state = request.args.get("state") expected = session.pop("oauth_state", None) # single use if not code or not state or not expected: abort(400, "Incomplete authorization response") if not hmac.compare_digest(state, expected): abort(400, "State mismatch - request abandoned") # Exchange server-side. Never send the code back to the browser. tokens = exchange_authorization_code(code) persist_tokens(session["user_id"], tokens) # Trust the returned scope array, not what you requested. return redirect("/settings/connections") ``` Exchange the code **immediately, once, from your server**. It is single-use and short-lived. Make your callback route idempotent — a user refreshing the page, a link prefetcher, or a browser retry will hit it twice, and the second attempt must not corrupt state or surface an error to the user who already succeeded. Next: [Exchange an OAuth authorization code](/exchange-an-o-auth-authorization-code). *** ## Step 4 — Reconcile what you actually got The exchange response includes the scope array the user approved. **That array, not your request, is the truth about what your integration can do.** A scope you requested can be missing because the user declined it, or because it isn't enabled on your app's Permissions tab — in which case it was silently dropped rather than rejected. Either way the flow completes successfully and your API calls fail later. Read the returned scopes, persist them alongside the tokens, and gate your features on them. If something essential is missing, tell the user plainly and offer to re-run the flow. *** ## Designing the moment The consent screen converts far better when the user understands why they're seeing it. * **Explain before you redirect.** One sentence on your own page — "Connect your Fluz account so we can send your payouts" — outperforms dropping someone cold onto a permissions screen. * **Trigger it in context.** At the point of first payout or first card, not buried in account settings. * **Full-page redirect over a popup.** Popups get blocked, and the flow includes 2FA and possibly identity verification, which is uncomfortable in a small window. If you need to stay in-page, use an [embedded widget](/developers/widgets) instead, which is built for exactly that. * **Handle the return trip.** Land the user where they were, with the thing they were trying to do now working. `state` is how you know where that was. * **Have a re-authorization path.** Refresh tokens expire and users revoke access. Build the "reconnect" flow at the same time as the connect flow, not after the first support ticket. * **Consider skipping it.** If your users don't already have Fluz accounts, a widget handles registration, verification, and consent in one hosted flow with no redirect. See [Embedded Widgets](/developers/widgets). *** ## Troubleshooting | Symptom | Almost always | | :------------------------------------------------------ | :------------------------------------------------------------------------------------------------ | | `/authorize` errors instead of rendering consent | `redirect_uri` isn't registered on the app, or `client_id` is from a different app or environment | | Consent screen shows fewer permissions than requested | Those scopes aren't checked on the Permissions tab — they were dropped, not rejected | | Scopes appear to be ignored entirely | Parameter was named `scope`; it must be `scopes` | | Scopes garbled or truncated | Delimiter not URL-encoded — spaces must be `%20` | | Callback arrives with no `state` | It wasn't sent on the authorize request | | `state` never matches | Session isn't sticky across the redirect, or it's being stored per-process behind a load balancer | | Exchange fails right after a clean consent | `redirect_uri` at exchange doesn't byte-match the one used at authorize | | Second callback hit throws an error at the user | Code already consumed — make the route idempotent | | Consent screen shows a placeholder name or blank avatar | **Overview** tab was never filled in | *** ## Next steps Turn the code into an access token and refresh token. Stay connected without sending the user back through consent. Fix anything the consent screen got wrong. Skip the redirect entirely with a hosted in-page flow. # Address Formatting Requirements Source: https://docs.fluz.app/concepts/address-formatting-requirements Addresses are submitted in several places across the Fluz API — user identity verification (KYC), business registration (KYB), funding sources, and virtual card issuance. This page defines how to format address fields so they are accepted, and the additional validation that applies specifically to card issuance. **Where this applies** Address fields use the same structure across the API. The **formatting rules** below apply everywhere. The **card issuance requirements** (US-only, no PO boxes, Smarty validation) apply only where noted. ## Address fields Every address object uses the same structured fields. Depending on the call, the object may be named `billingAddress`, `businessLegalAddress`, or an owner/individual address, but the field names are consistent. | Field | Required | Format | | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------- | | `streetAddressLine1` | Yes | Primary street address (building number + street). Must be a real, deliverable street address. | | `streetAddressLine2` | No | Apartment, suite, unit, or floor. Use a standard secondary designator (`Apt`, `Ste`, `Unit`, `Rm`, `Fl`, `#`). | | `city` | Yes | City name. Must be the city USPS associates with the ZIP code. | | `state` | Yes | Two-letter US state code (e.g. `NY`) or full state name. | | `postalCode` | Yes | 5-digit US ZIP code. Must belong to the city and state provided. | | `country` | Yes | Country name. Constraints vary by context — see [Country by context](#country-by-context). | ## Formatting rules These apply to **every** address you submit, in any section of the API: 1. **Use the structured fields.** Put the street on `streetAddressLine1` and the unit on `streetAddressLine2` — don't combine everything into one line. 2. **Use a real, deliverable address.** The building number must exist on the named street. A real street is not enough — `500 Main St` is invalid if the highest number on Main St is `480`. 3. **Keep city, state, and ZIP consistent.** The ZIP code must be the one assigned to that city and state. A mismatched ZIP is the most common cause of a rejected address. 4. **Use USPS abbreviations.** Standard suffixes (`St`, `Ave`, `Blvd`, `Dr`, `Ln`, `Rd`, `Ct`, `Pkwy`) and directionals (`N`, `S`, `E`, `W`, `NE`, `NW`, `SE`, `SW`) match most reliably. 5. **Put unit / apartment / suite in** `streetAddressLine2` using a recognized designator. If USPS requires a secondary unit for a building and none is provided, the address can fail to validate. 6. **Send a 5-digit ZIP** for US addresses. ZIP+4 is added automatically during standardization. ## Additional requirements for card issuance Virtual card billing addresses are validated and standardized through [Smarty](https://www.smarty.com/) (USPS Delivery Point Validation) before they are saved. This applies to [Add Virtual Card Address](/features/add-billing-address), [Create Virtual Card](/features/create-card), and [card issuance for authorized users](/features/create-virtual-card-for-authorized-user). * **US addresses only.** Set `country` to `United States`. * **No PO boxes.** The card issuer does not accept PO box addresses, even if USPS considers them deliverable. * **Must be Smarty-verified.** If the address cannot be matched to a real, deliverable US delivery point, the request is rejected with `VC-0025` and **no address is saved**. Retrying with the same values will fail again — the address itself must change. Bank card billing addresses ([Add Funding Sources](/features/add-bank-card)) share the same field structure and should match the address on file with the card issuer. A mismatched billing address can cause the card to be declined during the address verification (AVS) check. ## Country by context Not every address is US-only. Use the right constraint for the call you are making: | Context | Country constraint | | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | Virtual card billing address | US only (`United States`). | | Bank card billing address | Should match the card issuer's records (typically US). | | Business registration (KYB) `businessLegalAddress` | International addresses accepted (country name, ISO 3166). Some countries are restricted for KYB (e.g. Russia, Iran). | | User KYC verification | The user's residential address. | ## Why an address is rejected (`VC-0025`) For card issuance, an address fails when Smarty cannot confirm it as a deliverable US delivery point. The most common reasons: | Reason | What it means | | -------------------------------- | ---------------------------------------------------------------------------------------- | | City / State / ZIP mismatch | The ZIP code does not belong to the city or state provided. | | Invalid building number | The street exists, but the primary (building) number does not exist on it. | | Address not found | The street or address is not present in USPS data. | | Missing / invalid secondary unit | USPS requires an apartment or suite number for this building and it is missing or wrong. | | Non-deliverable point | The address maps to a vacant or otherwise undeliverable delivery point. | | PO box or non-US address | Rejected by the card issuer (PO boxes) or unsupported (outside the US). | ## Worked examples ### Example 1 — City / ZIP mismatch ```text theme={null} 2581 Oakwood Avenue, New York, NY 10605 ``` **Rejected (**`VC-0025`**).** ZIP code `10605` belongs to **White Plains, NY** (Westchester County), not New York City — so the city and ZIP do not match. In addition, `2581 Oakwood Avenue` is not a confirmed delivery point in that ZIP. Smarty returns no valid match, so the address is not saved. **Fix:** submit the ZIP that USPS assigns to the city (or the city that matches the ZIP), and a building number that exists on the street. ### Example 2 — Invalid building number ```text theme={null} 896 S State St, Dover, DE 19904 ``` **Rejected (**`VC-0025`**).** `S State St` exists in Dover, DE, but `896` is not a confirmed USPS delivery point for that street and ZIP. Because the specific building number cannot be validated, Smarty returns no deliverable match. **Fix:** confirm the exact building number and the ZIP that covers that block of the street. ### A valid submission ```json theme={null} { "billingAddress": { "streetAddressLine1": "1600 Amphitheatre Pkwy", "streetAddressLine2": "", "country": "United States", "city": "Mountain View", "state": "CA", "postalCode": "94043" } } ``` Saved and normalized (city, state, and postal code stored in uppercase; ZIP+4 added): ```json theme={null} { "streetAddressLine1": "1600 Amphitheatre Pkwy", "streetAddressLine2": null, "country": "United States", "city": "MOUNTAIN VIEW", "state": "CA", "postalCode": "94043" } ``` ## Handling `VC-0025` in your integration * **Collect the address in structured fields** rather than a single free-text field, so you can prompt for the specific part that needs fixing. * **Do not blind-retry.** The same address will keep returning `VC-0025`. Surface the error and ask the user to correct the address. * **Validate before you submit (optional).** Running an address autocomplete or verification step in your own UI reduces failed round-trips. * **Nothing is saved on failure**, so there is no cleanup before retrying with corrected values. ## Error reference | Error | Code | Message | | ------------------------- | --------- | ------------------------------------------------------------------------------------------------------------ | | `INVALID_BILLING_ADDRESS` | `VC-0025` | The billing address could not be verified. Please check the street, city, state, and ZIP code and try again. | See [Virtual Card Error Codes](/features/virtual-card-error-codes) for the full list. ## Next steps Known-good and known-bad addresses for exercising every validation outcome in staging. Where address errors fit in the broader error code namespace. *** **Want to learn more?** Contact us at [partnerships@fluz.app](mailto:partnerships@fluz.app). Speak with our experts for more info or to request a demo. # Authentication Source: https://docs.fluz.app/concepts/authentication How API keys, user access tokens, and scopes work — for your own account and for connected customer accounts. Every Fluz API request carries a **user access token** in the `Authorization` header. How you get that token depends on whose account you're operating on. ## Two paths, one API `API key` → `Your token` Your server uses its application API key (`Authorization: Basic `) to mint a token scoped to your Fluz account. `OAuth grant` → `Their token` A customer authorizes your app, and Fluz returns a token scoped to their account. Once you hold a token, the rest of the API is identical. `createVirtualCard` on your token creates a card on your wallet; `createVirtualCard` on a customer token creates it on theirs. ## Path 1 — your own account 1. In the dashboard, create an application and copy its **API Key**, **User ID**, and **Account ID**. 2. From your backend, call `generateUserAccessToken` on `https://transactional-graph.fluzapp.com/api/v1/graphql` (staging: `https://transactional-graph.staging.fluzapp.com/api/v1/graphql`) with the header `Authorization: Basic `, passing your `userId`, `accountId`, and the scopes you need as arguments. 3. Attach the returned `token` as `Authorization: Bearer ` on every request to the transactional graph. See [API credentials](/get-started/api-credentials) for a full example. ## Path 2 — a customer's account Use this when you're building a platform — e.g. issuing cards on behalf of your users. 1. Redirect the customer to Fluz's OAuth authorize URL with your `clientId`, requested scopes, and `redirect_uri`. 2. The customer signs in and grants your scopes. 3. Fluz redirects back with a short-lived authorization `code`. 4. Your server exchanges the code for a customer-scoped access token at the OAuth token exchange endpoint — see [the OAuth grant flow](/client-facing-o-auth-grant-flow). 5. Refresh customer-scoped tokens via the [OAuth token refresh endpoint](/refresh-o-auth-access-token) without re-prompting the customer. Full walkthrough in [Build a platform](/build-a-platform). ## Scopes Tokens carry an explicit set of scopes. Common ones by capability: | Area | Scopes | | --------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Gift cards | `LIST_OFFERS`, `PURCHASE_GIFTCARD`, `REVEAL_GIFTCARD`, `LIST_PURCHASES` | | Virtual cards | `CREATE_VIRTUALCARD`, `REVEAL_VIRTUALCARD`, `EDIT_VIRTUALCARD` | | Deposits | `MAKE_DEPOSIT` | | Funding sources | `LIST_PAYMENT`, `MANAGE_PAYMENT` | | Card data (PCI) | `PCI_COMPLIANCE` — granted at the application level to PCI-compliant developers; cannot be requested when generating a token | Request the minimum you need. Mint a new token when you need broader access. ## Token lifetime * **Access tokens:** short-lived JWTs (minutes, not hours). Mint a new one when it expires — see [Replace an expired access token](/get-started/refresh-expired-access-token). * **OAuth customer tokens:** refreshable via the [OAuth token refresh endpoint](/refresh-o-auth-access-token). * **Application API key:** valid until rotated in the dashboard. Never ship your API key to a browser or mobile client. It mints tokens for your account; leaking one is equivalent to leaking your credentials. ## Next steps Path 1 in practice — mint a token for your own account and make a call. Path 2 in practice — connect customer accounts with the OAuth grant flow. # Staging vs. Live Environment Source: https://docs.fluz.app/concepts/environments When integrating with the Fluz API, it's crucial to understand the distinction between the staging and live environments. These environments serve different purposes and are designed to ensure that your integration process is both secure and efficient. ## Staging Environment The Fluz staging environment lets you explore the platform's features and functionality. It is intended for experimenting, building integrations, and training your team. **Purpose:** The staging environment is used to simulate real-world scenarios. It mirrors the capabilities and transactions of the live environment but without using real money. This allows developers to test their integrations thoroughly before moving to production. **Data Usage:** Only use dummy or test data in the staging environment. This data should be explicitly created for testing purposes and should not include any production data such as customer details, financial data, or personally identifiable information (PII). **GraphQL Endpoint:** The staging environment has a dedicated GraphQL endpoint, which is separate from the live environment. Use this endpoint to interact with the API during development and testing. | Environment | Request URL | | ----------------- | ---------------------------------------------------------------- | | Staging (test) | `https://transactional-graph.staging.fluzapp.com/api/v1/graphql` | | Live (production) | `https://transactional-graph.fluzapp.com/api/v1/graphql` | ## Production Environment The live environment is the production environment where real transactions occur, and actions taken are final. To ensure the security and integrity of your business data, it is imperative not to use this environment for testing purposes. Production data includes sensitive information like customer details, financial data, or personally identifiable information (PII). **Purpose:** The live environment is where you can actively make purchases, earn cashback, and manage your account. All interactions in this environment are performed using real data and have real-world consequences. **Data Usage:** Only use real and verified data in the live environment. Ensure that all data is accurate and intended for production use, as actions in this environment will affect your actual account and cannot be undone. **GraphQL Endpoint:** The live environment has its own dedicated GraphQL endpoint, separate from the staging environment — see the endpoint table above. ## Important Considerations **Security:** To safeguard your data and your customers' privacy, never use production data in the staging environment. This includes avoiding the use of any real customer details, financial information, or PII in your test scenarios. **Transitioning from Staging to Live:** Before moving your application from staging to live, thoroughly test all functionalities in the staging environment. Ensure that your application behaves as expected in all scenarios and that any bugs are resolved. ## Ready to Get Started? Are you ready to start integrating with Fluz? **Follow these steps:** 1. **Create a Fluz Account:** If you don't already have an account, [sign up](https://go.fluzapp.com/?_branch_match_id=1233136580747331534\&utm_source=Website\&utm_medium=marketing&_branch_referrer=H4sIAAAAAAAAA8soKSkottLXz8rPzEvLKa3SSywo0MvJzMvWrwj3KIxyjQhxj7KvK0pNSy0qysxLj08qyi8vTi2ydc4oys9NBQD5fk6qPgAAAA%3D%3D) for one on the Fluz platform. 2. **Set Up Developer Access:** 1. Click your avatar in the top-right corner. 2. Navigate to 'Apps and Integrations.' 3. Click 'Create Developer Account' and fill out the required information. 4. Explore the Staging Environment: Use the staging environment to test your integration. Access the staging GraphQL endpoint and begin experimenting with Fluz API's features. 3. **Build and Test Your Application:** Develop your integration, test it thoroughly, and ensure it meets your business needs. 4. **Transition to the Live Environment:** Once testing is complete and you're confident in your application's performance, switch to the live environment. Remember, all actions in this environment will have real-world consequences. ## Next steps Test merchants, bank cards, bank accounts, KYC flows, and addresses — everything staging is stocked with. How to safely retry money-moving mutations without creating duplicates — in either environment. # How the GraphQL API works Source: https://docs.fluz.app/concepts/graphql One endpoint, queries and mutations, and the response format you can expect from every call. Fluz exposes a single GraphQL endpoint per environment. There are no per-capability base URLs — you send a query or mutation to `/api/v1/graphql`, and the token decides whose account you're operating on. ## Endpoints | Environment | URL | | ----------- | ---------------------------------------------------------------- | | Staging | `https://transactional-graph.staging.fluzapp.com/api/v1/graphql` | | Live | `https://transactional-graph.fluzapp.com/api/v1/graphql` | Access tokens are minted at the same endpoint via the `generateUserAccessToken` mutation, authorized with your API Key — see [Authentication](/concepts/authentication). ## Anatomy of a request ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "query GetSpendAccount($id: UUID!) { getUserCashBalanceById(userCashBalanceId: $id) { nickname availableCashBalance } }", "variables": { "id": "9c1f6b2e-4d7a-4c3b-9f11-2a5e8b0d6c74" } }' ``` * **`query`** — the GraphQL document. Use named operations (`query GetSpendAccount`) for better logs. * **`variables`** — typed inputs, keeping values out of the query string. * **`operationName`** — optional, useful when a document defines multiple operations. ## Queries vs. mutations * **Queries** are read-only (`viewer`, `wallet`, `transactions`). * **Mutations** change state (`createVirtualCard`, `purchaseGiftCard`, `depositCashBalance`, `createTransfer`). Money-moving mutations use request de-duplication — see [Idempotency](/concepts/idempotency). ## Response format GraphQL always returns HTTP 200 with a `data` / `errors` envelope: ```json theme={null} { "data": { "wallet": { "balance": { "value": 12500, "currency": "USD" } } }, "errors": null, "extensions": { "requestId": "req_01H..." } } ``` On failure, `data` may be `null` and `errors` will describe what went wrong — including a machine-readable `code` you can branch on. See [Errors](/api-reference/errors). ## Introspection and schema Introspection is enabled in staging so you can point tools like Apollo Studio or GraphiQL at the endpoint. In live, introspection is disabled; use the published schema from [API reference](/api-reference/overview) instead. ## Next steps How the token in that `Authorization` header gets minted — for your account or a customer's. What the `errors` array contains and how to branch on error codes. # Idempotency Requests Source: https://docs.fluz.app/concepts/idempotency An API call or operation is idempotent if it produces the same result regardless of how many times it's executed. This ensures that repeated calls do not cause unintended side effects, protecting against accidental duplicate requests. The Fluz API supports idempotency requests to safeguard against accidental duplication of operations. By using a client-generated, unique idempotency key, the API ensures that the same request is not processed multiple times within a 10-minute window if provided with the same idempotency key. ## How It Works When an idempotency key is provided with a request: * The server caches the initial response body and its status code. * If a duplicate request with the same idempotency key and response code is received, the API will return the cached result instead of re-executing the operation, especially if an error occurs within the operation. This mechanism helps prevent unintended duplicate processing by returning consistent results even in error scenarios. ## Generating Unique Idempotency Keys Clients are responsible for creating and refreshing their idempotency keys. To ensure uniqueness: * We strongly recommend using V4 UUIDs, which can be generated with libraries such as these: [https://github.com/uuidjs/uuid](https://github.com/uuidjs/uuid) * As a best practice, idempotency keys should expire after some time to avoid reuse and ensure the uniqueness of requests. Fluz expires each unique idempotency keys after 10 minutes. ### Clients will need: 1. An operation to generate and store a new idempotency key 2. An operation to remove an old key according to the client's expiration policy ## Required Operations Not all API operations require idempotency keys. Fluz requires idempotency for certain critical operations to ensure data consistency for the following operations: * `purchaseGiftCard` ([View here](/purchase-gift-card)) * `depositCashBalance` ([View here](/features/deposit-from-external-accounts)) For these operations, an `idempotencyKey` input variable is required. ## Example Input ```json theme={null} { "idempotencyKey": "2c29e5ad-bb36-4b7f-b012-b7d6031566e0", "offerId": "2c29e5ad-bb36-4b7f-b012-b7d6031566e0", "amount": 123.45, "balanceAmount": 987.65, "bankAccountId": "0285c162-fb2f-4c32-b076-29166471f570", "bankCardId": "2c29e5ad-bb36-4b7f-b012-b7d6031566e0", "paypalVaultId": "2c29e5ad-bb36-4b7f-b012-b7d6031566e0", "exclusiveRateId": "2c29e5ad-bb36-4b7f-b012-b7d6031566e0", "merchantSlug": "xyz789" } ``` Fluz requires an idempotency key for this operation. ## Next steps See the idempotency key in action on the most common money-moving mutation. The other common cause of rejected requests — how to format addresses so they validate the first time. # Rate limits Source: https://docs.fluz.app/concepts/rate-limits Fluz applies protective, per-service rate limits to keep the platform stable. What the limits are, what a 429 looks like, and how to build clients that stay within them. Fluz doesn't sell per-plan API quotas — there are no "Free = X req/min, Pro = Y req/min" tiers and no per-key quota system. Instead, each service applies a **protective rate limit** to absorb abuse and shield its backend. Normal interactive and application traffic effectively never hits these limits; they exist to catch runaway scripts and abuse. There are no published per-plan quotas to request an increase against. If a well-behaved integration is hitting limits, that usually points to a traffic pattern worth fixing (see [Build a well-behaved client](#build-a-well-behaved-client)) rather than a quota to raise. ## How limits are applied Limits are enforced independently **per service** and are evaluated globally across that service's instances — you can't escape a limit by landing on a different server. A request is identified by a combination of: * **Your IP address** — every request counts against a per-IP limit. * **Your access token** — the full `Authorization` header. Requests with no auth header skip the token limiter but still count against the IP limiter, so send a stable token to be limited as *you* rather than lumped in with everyone sharing an IP. * **The endpoint path** — on some public surfaces, specific paths have their own limits. Exceeding any limit returns **HTTP `429 Too Many Requests`**. ## Traffic limits by surface These are sustained per-second limits. When exceeded, the key is blocked for a short cooldown before requests are accepted again. | Surface | Limit | Cooldown after exceeding | | ---------------------------------------------------------------------- | --------------------------------------- | ------------------------ | | **GraphQL API** — the transactional graph endpoint (`/api/v1/graphql`) | 20 requests / second | 10 seconds | | **Gift card vendor operations** | 20 requests / second | 10 seconds | | **Social graph** (contacts, invites, follows) | 50 requests / second | 10 seconds | | **Other services** (default) | 10 requests / second | next request rejected | | **Mobile / app gateway** | Protective limits tuned per environment | — | The GraphQL API endpoint is the one most integrations call for deposits, purchases, transfers, and reveals — plan around **20 requests per second** there. The mobile/app gateway's per-IP, per-token, and per-endpoint limits are configured per environment and aren't published as fixed numbers; treat them as protective and back off on `429`. ## Authentication & security Sign-in, PIN, and two-factor (2FA) flows are protected separately from general API traffic. Repeated failed attempts — logins, PIN checks, or 2FA verification — and excessive 2FA or SMS requests trigger temporary lockouts that clear on their own after a cooldown. Successful authentication resets these counters. Don't auto-retry failed logins, PIN checks, or 2FA. Surface a "try again later" state to the user — automatic retries extend the lockout. ## When you exceed a limit Every rate-limited response uses status **`429 Too Many Requests`**. The body and headers vary by surface: ```json GraphQL / web / vendor services theme={null} { "message": "Rate limit exceeded" } ``` ```json Gateway / auth services (standardized error) theme={null} { "success": false, "msg": "Too many requests, please try again later", "errorRecord": { "code": "G-0002", "name": "RatelimitExceeded" } } ``` Header availability is not uniform: * **`Retry-After`** (integer seconds) is returned on the authentication IP limiter and the PIN/2FA flows. It is **not** guaranteed on the general GraphQL/web traffic limiters. * There are currently **no** `X-RateLimit-Limit`, `X-RateLimit-Remaining`, or `X-RateLimit-Reset` headers — don't build logic that depends on them. ## Build a well-behaved client Honor `Retry-After` when it's present. When it isn't, back off exponentially starting around 1 second (1s → 2s → 4s…) rather than retrying immediately. Don't parallel-blast a surface from a single IP or token. For the GraphQL API, stay comfortably under 20 requests/second and smooth out bursts. Repeated login, PIN, or 2FA failures cause temporary lockouts. Show the user a "try again later" message instead of retrying automatically. Authenticate every request with your token so token-based limits apply to your traffic specifically, instead of being aggregated with others behind a shared IP. Because `Retry-After` isn't guaranteed everywhere, pair your retry logic with [idempotency keys](/concepts/idempotency) so a retried mutation never double-processes a purchase or transfer. For the full error catalog, see [Errors](/api-reference/errors). # Connector behaviour Source: https://docs.fluz.app/connector-behaviour Status values, error responses, timing, limits, and the request constraints that apply to every connector. Connectors accept your vendor's request shape and return your vendor's response shape. A few things are Fluz's rather than the vendor's, and they are the same across all three gift card connectors. Read this page before you cut over. ## Authentication and account scope Every connector uses HTTP Basic authentication with your Fluz API key: ```text theme={null} Authorization: Basic ``` A key is issued for exactly one Fluz account. That account funds every order placed with the key, and every read is scoped to it. Fields your vendor used to route between sub-accounts, such as Tango's `accountIdentifier`, do not select a sub-account here. If you operate several accounts, request one key per account. ## Order status values The `status` field on an order (`OrderStatus` on InComm) carries a Fluz value, not your vendor's: | Status | Meaning | | :------------ | :------------------------------- | | `PENDING` | Accepted, not started | | `IN_PROGRESS` | Being fulfilled | | `COMPLETED` | Fulfilled, credentials available | | `FAILED` | Not fulfilled | | `CANCELED` | Cancelled before fulfilment | Your vendor's status strings do not carry over. A check written against a vendor value, for example `status === "COMPLETE"`, will not match `COMPLETED`. Update every status comparison before you cut over. ## Timing **Tango Card and InComm** create an order synchronously, always running to completion. The call does not return until the purchase has finished, which can take up to 150 seconds, so set your client's read timeout above that. **Runa** is asynchronous by default. `POST /v2/order` returns `202` immediately with a reference ID, and you read the order later. Send `X-Execution-Mode: sync` to block until the purchase completes and receive the full result, as Tango and InComm do. ## Reading an order An order becomes readable once its purchase has completed. Reading a reference ID before then returns an error rather than a pending status, so treat an error on a freshly created asynchronous order as still processing and read again shortly. The list endpoints return the 100 most recent orders on the account in a single response, newest first. ## Error responses Errors use the Fluz envelope, not your vendor's error schema: ```json theme={null} { "error": "" } ``` | Code | When | | :---- | :------------------------------------------------------------------ | | `401` | Missing or invalid credentials | | `429` | Rate limit exceeded. Body is `{ "message": "Rate limit exceeded" }` | | `500` | Every other failure, including a request the connector rejects | A rejected request and a server fault both return `500`. Read the `error` message to tell them apart: a rejection means the request needs to change, a fault is safe to retry. ## Rate limits 20 requests per second, applied per API key and per source IP. Exceeding either limit blocks that key or IP for 10 seconds, so back off for at least that long after a `429`. The per-key limit is shared, so several hosts using one key share one budget. ## Balances A balance read reports the available balance of the Fluz account your API key is issued for. Tango returns it as `currentBalance` and Runa as `balance`; InComm returns `availableBalance` alongside `prepaidBalance`, which covers the gift card balance on its own. A balance read is always scoped to the account your key is issued for, so the discriminators in your vendor's balance calls do not narrow it further: Tango's `:accountId`, InComm's `:programId` and Runa's `?currency=` are accepted for compatibility with your existing request shape. One behaviour to watch: a balance call with any query string returns a single object, and a call with none returns an array of one. If your client calls `.map()` over the response, keep the query string off. ## Brand codes All three connectors resolve brand codes against one Fluz catalog, whichever field carries them: `utid` on Tango, `Sku` on InComm, `items[].products.value` on Runa. Your vendor's product codes do not carry over, and a code Fluz does not recognise returns an error. Map your full brand list against the Fluz catalog before you cut over. A code that resolves to the wrong Fluz offer delivers the wrong card without an error. ## Request constraints Connectors accept your vendor's request shape, but some values are fixed. A request that breaks one of these is rejected. | Connector | Constraint | | :--------- | :------------------------------------------------------------------------------------------------------- | | Tango Card | `sendEmail` must be present and set to `false` | | Runa | `payment_method` must be `{ "type": "ACCOUNT_BALANCE", "currency": "USD" }` | | Runa | `products.type` must be `SINGLE` | | Runa | Every entry in `items[]` must be identical. Mixed baskets are rejected. Send one order per distinct item | | InComm | Exactly one entry in `Recipients[]` | | InComm | Exactly one entry in `Products[]` | | InComm | `DeliverEmail` must not be `true` | Fluz returns gift card credentials directly in the order response, which is why the email flags above must be off: delivery to the recipient stays under your control. # Configure OAuth App Source: https://docs.fluz.app/create-an-o-auth-app Set up your application's identity, scope ceiling, redirect URIs, and webhook endpoints — everything that has to be right before a user can grant you access. Creating an application registers it. Configuring it is what makes it work. This page walks the four tabs of the app editor in the order you should fill them out, and explains what each field actually controls. Get these right before you build your authorize flow — most OAuth integration failures trace back to a configuration mismatch rather than to code. Reach the editor from **'Your apps'** in the developer dashboard, or directly at `https://fluz.app/for-developers/overview/{appId}`. Embedded widgets use the same tabs plus an additional **Installation** tab — see [Configure App Widget](/developers/configure-app-widget). Selecting an app to configure from Your apps *** ## What's on each tab | Tab | What it controls | Read it before you | | :--------------- | :---------------------------------------------------------- | :---------------------- | | **Overview** | Your app's public identity, and where your credentials live | Write any code | | **Permissions** | The maximum scopes your app may ever request | Build the authorize URL | | **OAuth** | Origin, redirect URIs, webhook endpoints | Handle the callback | | **Installation** | Generated embed code *(widgets only)* | Embed the widget | *** ## Overview tab Two things live here, and they serve very different audiences. ### Your credentials The **Client ID** and **Client Secret** are on this tab. Every other page in this section that tells you to authenticate a request with `Authorization: Basic base64(client_id:client_secret)` means these values. The **API Key** and **API Secret** also live here — those are a separate pair with a separate job. See [OAuth Applications](/build-a-platform/oauth-applications-overview) for which credential does what. Copy them into your secret manager. Never into a browser bundle, a mobile binary, or source control. ### Your public identity The name, subtitle, description, avatar, and logomark are **what your users see on the consent screen** when they decide whether to hand your application access to their money. Treat these as product copy, not internal labels. | Field | Guidance | | :-------------- | :----------------------------------------------------------------------------------------------- | | **App name** | The name your users already know you by. "Acme Payouts," not "acme-oauth-prod-v2." | | **Subtitle** | One line on what the app does for them. | | **Description** | A short, plain-language account of why you're asking for access. | | **Avatar** | Required in practice. A consent screen with a blank avatar looks unfinished, and users hesitate. | | **Logomark** | Add it alongside the avatar. | Review this tab even if you think you filled it out during creation. The three text fields are collected in the creation wizard, where it's easy to type a placeholder and move on — and then ship it to a consent screen. *** ## Permissions tab This tab sets your **scope ceiling**: the maximum set of permissions your application may ever request from any user. It is not what any individual user has granted you. Scope selection on the Permissions tab ### How selected scopes reach the user Users don't see raw enum values. Scopes you select are **grouped under a readable top-level header**, and it's the header that's presented for approval. A scope left unchecked is omitted entirely from what the user is asked to approve — and from what your app can ever request. How grouped scopes appear on the consent screen ### Required scopes Some scopes are mandatory for the app or widget type you're configuring — without them the flow physically cannot run. These are gathered at the bottom of the tab, and **the user cannot unselect them** on the consent screen. You'll see them there; you don't choose them. ### Choosing your scopes Start from what the flow in front of you needs, not from what you might need someday. | If your integration... | Request roughly | | :-------------------------------- | :---------------------------------------------------------------------- | | Pays users out from your platform | `MAKE_WITHDRAW`, `LIST_PAYMENT` | | Collects funds from users | `MAKE_DEPOSIT`, `LIST_PAYMENT` | | Issues cards on a user's wallet | `CREATE_VIRTUALCARD`, `EDIT_VIRTUALCARD`, `REVEAL_VIRTUALCARD` | | Distributes cards by link | `CREATE_VIRTUALCARD`, `CREATE_SHARE_LINK` | | Sells or redeems gift cards | `LIST_OFFERS`, `PURCHASE_GIFTCARD`, `REVEAL_GIFTCARD`, `LIST_PURCHASES` | | Manages a user's funding sources | `LIST_PAYMENT`, `MANAGE_PAYMENT` | Full reference: [Application Scopes](/fluz-dashboard/application-scopes). **Fewer scopes convert better.** The consent screen is the highest-drop-off step in your integration, and its length is set by this tab. A narrower ceiling also limits the blast radius if a token leaks. Ask for what today's flow needs; widen the ceiling when you build the next feature. ### Scopes you can't self-select `PCI_COMPLIANCE` is administered by Fluz at the application level, granted to developers who have demonstrated PCI DSS compliance, and cannot be requested when generating a token. If you need to handle raw card data yourself, talk to your Fluz account manager. If you don't want to, that's what [embedded widgets](/developers/widgets) are for — they keep card capture inside Fluz's PCI scope. ### Changing scopes later The permission model is an **intersection** of the app-level ceiling and each user's grant, which has two practical consequences: * **Adding** a scope here does *not* retroactively grant it on tokens users already issued you. Existing users must re-authorize before the new scope becomes effective for them. * **Removing** a scope here narrows effective access immediately, for every user, regardless of what they previously approved. Plan scope changes like schema migrations, not like config tweaks. *** ## OAuth tab Redirect URI configuration on the OAuth tab ### Origin The domain that will host the flow — `example.com`, `app.example.com`. For embedded widgets this is the page the widget renders on, and it must match or the widget won't load. ### Redirect URIs Where our authorization server is permitted to send the user after they approve or decline. * Must be a **public** URL our servers can reach. * **No query parameters** on the registered URI. Use the `state` parameter to carry context instead. * Register **as many as you need** — one per environment, one per flow variant. * The URI you use at `/authorize` must be registered here, and the URI you send to `/token/exchange` must be **byte-identical** to the one you used at `/authorize`. These are four different URIs as far as the authorization server is concerned: ```text theme={null} https://app.example.com/oauth/finalize https://app.example.com/oauth/finalize/ http://app.example.com/oauth/finalize https://App.Example.com/oauth/finalize ``` Pick one canonical form, store it in a single constant, and use that same constant in both the authorize step and the exchange step. Hard-coding it twice is how mismatches happen. Register your local callback explicitly — for example `http://localhost:3035/oauth/finalize`. It won't work unless it's on the list, and localhost URIs should not be left registered on a production app. ### Webhook URLs Public REST endpoints that receive events from Fluz — how you learn that a transfer completed, a user dismissed a modal, or a verification resolved, without polling. * Add **as many URLs as you like**. * Subscribe each URL to **specific events**, so you can route different event families to different services. * A URL with **no events selected becomes a catch-all** and receives everything. Convenient for development, noisy in production. Your endpoint should acknowledge quickly and process asynchronously. Do the minimum work needed to accept the event, then hand it to a queue — a slow webhook handler turns into a delivery problem. *** ## Verify before you build Five minutes here saves an afternoon of debugging the authorize flow. Client ID, Client Secret, API Key, API Secret. Confirm nothing landed in a `.env` that's tracked in git. Name, subtitle, description, avatar, logomark all populated and written for your users. Walk your intended API calls and confirm each one's scope is enabled. A scope you request but haven't enabled here is silently dropped, not rejected — so this mistake surfaces later as a confusing permission error. Including protocol, host casing, and trailing slash. Assemble `/authorize` with `response_type=code`, your `client_id`, your registered `redirect_uri`, and your scopes, then load it in a browser. If the consent screen renders with your branding and the scopes you expect, your configuration is correct. If it errors, the message names what didn't match — and you've found it before writing any code. → [Client-facing OAuth grant flow](/client-facing-o-auth-grant-flow) *** ## Staging and production are separate apps Environments do not share configuration. A production application is registered separately, with its own Client ID, Client Secret, API Key, and API Secret, and its own redirect URIs and webhook endpoints pointed at live hosts. Nothing carries over from staging — including scope selections. Re-verify this entire page against your production app before launch. See [Deploying to Production](/deploying-to-production). *** ## Troubleshooting | Symptom | Almost always | | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/authorize` returns an error instead of a consent screen | `redirect_uri` isn't registered, or `client_id` doesn't match this app | | Consent screen shows fewer scopes than you requested | The missing ones aren't checked on the Permissions tab | | Exchange at `/token/exchange` fails | `redirect_uri` doesn't byte-match the authorize step, or the Basic auth header encodes the parts separately instead of `base64(client_id:client_secret)` | | API call fails on a permission you thought you had | Scope is enabled at app level but wasn't in this user's grant — check the `scope` array returned by the exchange, not what you requested | | `generateUserAccessToken` fails for a user who worked before | The app-level or user-level grant expired or was revoked. Both must be live | | Widget won't load on your page | **Origin** doesn't match the hosting domain | | No webhooks arriving | URL isn't publicly reachable, or it's subscribed to events other than the ones firing | *** ## Next steps Build the authorize URL and handle the callback. Turn a code into an access token and refresh token. The full scope reference. Re-register against live hosts and go live. # Custom card art Source: https://docs.fluz.app/custom-card-art Submit your own branded card design for the cards Fluz issues to your customers, and get it approved by the bank and the network. Programs that issue cards under their own brand run a **co-brand** design through Fluz. You supply the artwork and supporting documents; Fluz files them with our processor, sponsor bank, and the card network, and returns a card profile set ID that ties your design to the cards you create. Custom card art is reviewed and approved by parties outside Fluz — the sponsor bank and Visa or Mastercard. Plan for **6–8 weeks** from a complete submission to a production card profile. Do not build launch marketing around an unapproved design. ## Before you start Cards issued on the standard Fluz design need no submission at all. You only need this process if your customers will see your brand on the card. Virtual, digital wallet (tokenized), and physical cards each have their own artwork requirements and their own approval steps. Submit only what you will actually issue — every extra type adds review time. Programs in crypto, cannabis, gaming, and similar verticals are subject to enhanced review, and the design itself is reviewed for anything that could be read as misleading or as an implied endorsement. See [specialized verticals →](/specialized-verticals) Card art review runs after your entity clears compliance and bank review. Starting the design in parallel is fine; submitting it before due diligence is not. [Due diligence →](/build-a-platform#due-diligence) ## What to submit ### Documents | Document | Completed by | Notes | | ----------------------------------- | ------------ | ---------------------------------------------------------------------------------------- | | Co-brand card program questionnaire | Fluz | We complete this using details you provide about the program, audience, and launch plan. | | Bank vendor ownership form | You | Waived for publicly traded companies. | | Digital wallet terms and conditions | You | Required only if you are issuing tokenized cards to Apple Pay or Google Wallet. | | Marketing materials | You | Landing pages, user flows, and any customer-facing collateral that shows the card. | ### Artwork files | File | Format | Specification | | ------------------- | ------ | ----------------------------------------------------------------------------------------- | | Card image with PAN | PNG | 1536 × 969 px, showing the full 16-digit sample PAN, CVV, and expiration date in position | Virtual card art must show a **full 16-digit PAN** — networks do not permit a truncated PAN on a virtual card face. | File | Format | Specification | | ----------------------- | ------ | --------------------------------------------------- | | Card image, last 4 only | PNG | 1536 × 969 px, displaying only the last four digits | | Card image, no PAN | PNG | 1536 × 969 px, no card number | | App icon | PNG | 100 × 100 px | All three files are required. Wallet providers use them in different surfaces. | File | Format | Specification | | ------------------- | ----------------------- | ------------------------------ | | Front of card (FOC) | Adobe Illustrator (.ai) | Full 16-digit sample PAN | | Back of card (BOC) | Adobe Illustrator (.ai) | Full 16-digit sample PAN | | Card carrier | High-resolution PDF | The mailer the card arrives on | Physical issuance is not enabled on every Fluz program. Confirm availability with your Fluz contact before you commission physical artwork. ### Sample PAN convention Artwork proofs must use the network's reserved sample number so the file is never mistaken for a live account. | Network | Sample PAN must end in | | ---------- | ---------------------- | | Mastercard | `x3456` | | Visa | `x9010` | ## Network requirements Both networks specify what has to appear on the card face and where. A file that omits any required element is rejected at network review, not at bank review — which costs you the full cycle. | Element | Requirement | | ------------------ | ------------------------------------------------------------------------------------------------------ | | Brand mark | Mastercard symbol, Mastercard Premium brand mark, Maestro, or Cirrus. Minimum 3 mm from the card edge. | | Product identifier | 8.8 mm in size, at least 4 mm from the brand mark. | | PAN | Full 16 digits. | | CVC/CVV | 3 digits. | | Expiration date | Required. | Additional design references are available in the [Mastercard Design Center library](https://www.mastercard.us/en-us/business/overview/support/design-center.html). | Element | Requirement | | ----------------------- | ------------------------------------------------------------------------------ | | Brand mark | Minimum 3 mm from the card edge, rendered in Title Case. | | Cardholder name | Required. | | PAN | Full 16 digits. | | CVC/CVV | 3 digits. | | Expiration date | Required. | | Virtual account marking | Optional. If used, "Visa Virtual Account" or "Virtual Account", in Title Case. | **Limited use cards.** If the card is restricted — card-not-present only, for example — that restriction has to be disclosed. Displaying "Limited Use" on the card face is optional, but where it does appear it must be clearly visible; otherwise the disclosure belongs in your cardholder materials or in-app. Fluz reviews your disclosure copy alongside the artwork. Visa programs carry one extra approval step (BMAS) that Mastercard programs do not. Budget an additional 1–2 weeks. ## Design rules Design against a neutral background. Card art is rendered in surfaces you do not control — a design that depends on a specific backdrop will not survive them. Keep the PAN, CVV, expiration date, and cardholder name legible against whatever sits behind them. Contrast failures are the most common rejection we see. Hold all brand marks and required elements inside the safe area. Nothing may crowd or overlap the network brand mark. Use only marks you have the right to use. Third-party logos require documented permission from the mark holder. Avoid anything that implies a benefit, guarantee, or affiliation the program does not actually provide. ## Approval timeline Each stage runs in sequence, and a rejection at any stage sends the file back to the start of that stage. Basic KYB on the co-brand entity, confirming registration matches the submitted documents and surfacing reputational risk. The sponsor bank runs its own KYB scan on the co-brand entity and its own reputational risk assessment. Card art, carrier, and marketing materials are reviewed separately from the entity. This is the longest single stage. Filed with the network: a PIF for Visa, an RPP for Mastercard. Digital card art is submitted to Visa through BMAS. Mastercard programs skip this stage. Once bank approval lands, a card profile set ID is created for your program. This is what guarantees your cards issue with your art. **Additional setup, where applicable** | Item | Duration | Prerequisite | | ---------------------------------------- | ------------------ | -------------------------------------------------- | | Digital wallet fulfillment profile | 5–10 business days | Bank and network approval of the digital card art | | Physical card profile | 2 business days | Bank and network approval of the physical card art | | Tokenization (Apple Pay / Google Wallet) | Up to 8 weeks | Program approvals and wallet provider setup | Tokenization is the long pole. If push provisioning is part of your launch, start it as early as the approvals allow. See [digital wallet push provisioning →](/digital-wallet-push-provisioning) ## After approval Your program is assigned a **card profile set ID**. Fluz attaches it to your platform credentials, so cards created on your program automatically carry your approved design — no change to your integration. Any later change to an approved design is a new submission and runs the full bank and network cycle again. Colors, logos, layout, and disclosure copy are all in scope. Get the design right before you submit rather than iterating through review. ## Submit your card art Send your artwork files, supporting documents, and marketing collateral to your Fluz contact, or email us directly and we will open the submission for you. Email [humans@fluz.app](mailto:humans@fluz.app) with your program name, the card types you are issuing, and your artwork files attached. Questions on a design before you commit to it? Reach your Fluz contact or write to [humans@fluz.app](mailto:humans@fluz.app) — a five-minute look at a draft is cheaper than a rejection six weeks in. # Deploying to Production Source: https://docs.fluz.app/deploying-to-production Move your integration from staging to live — the endpoint map, the credentials you have to reissue, the app configuration you have to redo, and what to verify before real money moves. Going live is a configuration cutover, not a rewrite. Your queries, mutations, and flows are identical in both environments. What changes is **which hosts you call** and **which credentials you call them with** — and those change completely. Nothing carries over from staging. Applications, API keys, client secrets, redirect URIs, and webhook subscriptions all exist separately in each environment. A staging credential will never work against a production host, and vice versa. That's deliberate — it's what makes it impossible to accidentally move real money from a test harness. ## Endpoint map | Purpose | Staging | Production | | :------------------ | :--------------------------------------------------------------- | :------------------------------------------------------- | | Developer dashboard | `https://uni.staging.fluzapp.com/apps-and-integrations` | `https://fluz.app/for-developers` | | OAuth authorize | `https://uni.staging.fluzapp.com/authorize` | `https://fluz.app/authorize` | | Token exchange | `https://uni.staging.fluzapp.com/token/exchange` | `https://fluz.app/token/exchange` | | Token refresh | `https://uni.staging.fluzapp.com/token/refresh` | `https://fluz.app/token/refresh` | | GraphQL API | `https://transactional-graph.staging.fluzapp.com/api/v1/graphql` | `https://transactional-graph.fluzapp.com/api/v1/graphql` | The GraphQL host and the OAuth host are different services with different domains. Swapping one and not the other is the single most common cutover mistake — and it fails in a confusing way, because authorization succeeds and then every API call is rejected. ### Reaching each portal The live portal is at `https://fluz.app/for-developers`. From there, the developer tab has an **'Open Staging'** link into the staging portal — your login credentials are the same in both. Each portal shows only that environment's applications and API keys. *** ## Make the environment a variable If any host, key, or redirect URI is a string literal in your codebase, fix that before cutting over. Everything environment-specific belongs in configuration. ```javascript Node.js theme={null} const ENVIRONMENTS = { staging: { authorizeUrl: 'https://uni.staging.fluzapp.com/authorize', tokenExchangeUrl: 'https://uni.staging.fluzapp.com/token/exchange', tokenRefreshUrl: 'https://uni.staging.fluzapp.com/token/refresh', graphqlUrl: 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql', redirectUri: 'https://staging.example.com/oauth/finalize', }, production: { authorizeUrl: 'https://fluz.app/authorize', tokenExchangeUrl: 'https://fluz.app/token/exchange', tokenRefreshUrl: 'https://fluz.app/token/refresh', graphqlUrl: 'https://transactional-graph.fluzapp.com/api/v1/graphql', redirectUri: 'https://app.example.com/oauth/finalize', }, }; export const fluz = ENVIRONMENTS[process.env.FLUZ_ENV ?? 'staging']; // Credentials come from your secret store, keyed by the same environment name. export const credentials = { clientId: process.env.FLUZ_CLIENT_ID, clientSecret: process.env.FLUZ_CLIENT_SECRET, apiKey: process.env.FLUZ_API_KEY, apiSecret: process.env.FLUZ_API_SECRET, }; ``` ```python Python theme={null} import os ENVIRONMENTS = { "staging": { "authorize_url": "https://uni.staging.fluzapp.com/authorize", "token_exchange_url": "https://uni.staging.fluzapp.com/token/exchange", "token_refresh_url": "https://uni.staging.fluzapp.com/token/refresh", "graphql_url": "https://transactional-graph.staging.fluzapp.com/api/v1/graphql", "redirect_uri": "https://staging.example.com/oauth/finalize", }, "production": { "authorize_url": "https://fluz.app/authorize", "token_exchange_url": "https://fluz.app/token/exchange", "token_refresh_url": "https://fluz.app/token/refresh", "graphql_url": "https://transactional-graph.fluzapp.com/api/v1/graphql", "redirect_uri": "https://app.example.com/oauth/finalize", }, } FLUZ = ENVIRONMENTS[os.environ.get("FLUZ_ENV", "staging")] # Credentials come from your secret store, keyed by the same environment name. CREDENTIALS = { "client_id": os.environ["FLUZ_CLIENT_ID"], "client_secret": os.environ["FLUZ_CLIENT_SECRET"], "api_key": os.environ["FLUZ_API_KEY"], "api_secret": os.environ["FLUZ_API_SECRET"], } ``` Default to staging, never to production. If a deployment loses its environment variable, you want it hitting the test environment, not moving real money. *** ## Rebuild your app in the live portal Work through [Create an OAuth App](/create-an-o-auth-app) again against `https://fluz.app/for-developers`. Then check every one of these — each is something people forget: Production `client_id`, `client_secret`, `apiKey`, and `apiSecret` are all new values. Put them in your production secret store. Confirm no staging value survives in a production config. Your production callback URL, in its exact canonical form. Then **remove any `localhost` or staging URIs** — a production app should not accept a redirect to a developer's laptop. At production endpoints that are publicly reachable, monitored, and alerting. Re-do the per-event subscriptions; they don't copy across. If you used a catch-all URL in staging, decide whether you actually want that in production. For embedded widgets, this must match the domain actually serving the page, or the widget won't load. Scope selections don't transfer. Walk your integration's API calls and confirm each one's scope is checked on the production app's Permissions tab. A missing scope is silently dropped, not rejected. Name, subtitle, description, avatar, and logomark are what real users now see on a real consent screen deciding whether to give you access to their money. Placeholder text ships to production if you let it. Applications carry a status in the dashboard — an app sitting in review is not yet an app your customers can use. Confirm your production app is active before you announce anything. *** ## What behaves differently in production Staging mirrors production's capabilities and transaction flows without real money. That's most of the surface, but not all of it. | | Staging | Production | | :------------------ | :----------------------------------- | :------------------------------------------------------------------------- | | Funds | Simulated | Real. Transfers are irreversible on your side | | KYC | Test paths with predictable outcomes | Real verification against real identity data, with real declines | | Card authorizations | Simulated | Live network authorizations and real declines | | Settlement timing | Immediate or simulated | Real banking timelines | | Funding sources | Test bank cards and accounts | Real cards and Plaid-linked accounts | | Failure modes | The ones you chose to test | Insufficient funds, expired cards, network declines, verification failures | Three consequences worth planning for: * **Idempotency stops being optional.** Every money-moving call needs a unique `idempotencyKey`, and widget tokens need a unique `jti`. In staging a duplicate is a nuisance; in production it's a double payment. See [Idempotency](/docs/idempotency-requests). * **Your error handling gets exercised.** Test users don't get declined for insufficient funds or fail KYC in ways you didn't script. Every failure path needs a defined user-facing outcome before launch, not after. * **Reconciliation matters.** Verify balances on both sides of a transfer rather than assuming success from a 200 response. *** ## Data hygiene, both directions **Never put production data in staging.** No real customer details, no real financial information, no PII. Staging is for data created explicitly for testing. The reverse also holds: don't carry test users, test funding sources, or test webhook payloads into production. Test artifacts in a live ledger are hard to unpick later, and some of them can't be deleted. *** ## Widget cutover If you're shipping an [embedded widget](/developers/widgets), the same rules apply plus these: * **Regenerate the embed code from the production app's Installation tab.** The `apiKey` baked into the snippet is environment-specific. * **Sign `patToken`s with your production `apiSecret`**, server-side. Confirm the secret is loaded from your production secret store and that the token generator is not still pointing at a staging value. * **Confirm `Origin`** matches your production domain exactly. * **Re-check the transaction type.** Pay-In and Payout move money in opposite directions; verify the direction against a real transfer before you open it to users. *** ## Pre-launch checklist * Production app created, configured, and active * All four credentials reissued and stored in the production secret store * No staging or localhost redirect URIs remain on the production app * Webhook URLs point at production endpoints, with event subscriptions re-selected * Scopes re-selected and matched against your actual API calls * Overview tab complete — name, subtitle, description, avatar, logomark * No hard-coded hosts, keys, or redirect URIs anywhere in the codebase * Environment resolution defaults to staging * Both the OAuth host and the GraphQL host swapped * Idempotency keys generated per operation, not per session * Callback route is idempotent and validates `state` * Token refresh runs before expiry rather than reacting to a failure * Webhook endpoint monitored, with alerting on failure to deliver or process * Logging captures request identifiers and idempotency keys, and never captures secrets, PANs, or PII * Someone owns the "user can't connect" and "transfer stuck" runbooks * You've run one real end-to-end transaction at the smallest possible amount, in both directions, and reconciled both ledgers * Internal users first, then a small cohort, then general availability * You can disable the integration without a code deploy — a feature flag, not a rollback * Re-authorization path built and tested, for when refresh tokens expire or users revoke *** ## Common cutover failures | Symptom | Cause | | :----------------------------------------------------- | :-------------------------------------------------------------------------------- | | OAuth succeeds, then every API call is rejected | GraphQL host still pointed at staging (or vice versa) | | `/authorize` errors on the production URL | Production app's redirect URI not registered, or `client_id` is the staging value | | Token exchange fails immediately after a clean consent | Basic auth header built from staging `client_id`/`client_secret` | | Widget won't render on the live site | `Origin` still set to the staging or localhost domain | | Widget opens but the transaction is rejected | `patToken` signed with the staging `apiSecret` | | A permission that worked in staging now fails | Scopes weren't re-selected on the production app | | No webhooks in production | URLs weren't re-pointed, or event subscriptions weren't re-selected | | Consent screen shows a placeholder app name | Production Overview tab never filled in | | Duplicate transfers | Idempotency key reused or regenerated per retry | *** ## Next steps Re-run every tab against your production app. Verify the consent screen on production hosts. Keep production connections alive without re-prompting. Everything now running against real money. # Tools that shorten the path to production Source: https://docs.fluz.app/developers Embeddable widgets for low-code launches, drop-in connectors for APIs you already integrate with, and real-time event notifications. Fluz gives you three ways to move money in and out of your product without building the money-movement UI or plumbing yourself. Pick the entry point that matches how you build. Drop a JavaScript widget into your page for card and account flows. Point existing Tango, Stripe, Worldpay, Venmo, or PayPal integrations at Fluz. Webhooks for real-time updates on transactions, cards, and accounts. ## Good things to know * [GraphQL](/concepts/graphql) * [Authentication](/concepts/authentication) * [Staging vs. live](/concepts/environments) * [Idempotency](/concepts/idempotency) * [Rate limits](/concepts/rate-limits) * [Error codes](/get-started/general-api-error-codes) * [Address formatting](/concepts/address-formatting-requirements) * [Sandbox test data](/test-bank-cards) ## Code examples Copy-paste snippets for individual API operations — tokens, cards, gift cards, KYC, and money movement. Single-operation GraphQL scripts you can drop straight into your integration. **AI-native docs:** point your assistant at `llms.txt` for a machine-readable index, or connect via MCP at `docs.fluz.app/mcp`. # Add A New App Widget Source: https://docs.fluz.app/developers/add-a-widget Follow these steps to create and manage your new Fluz Widget. * Access your developer dashboard by selecting 'For developers' from your profile menu. * Select '[Browse templates](https://fluz.app/for-developers/templates) '. You will see all available widget templates listed here. * Click on the widget you would like to configure. For example, click 'Withdraw to a virtual Master'. This will open a wizard allowing you to customize the 'Withdraw to a virtual Master' app widget. * Fill out the App name * Fill out a subtitle of your app * Fill out the app Description * Click "Create new app" * Your new app widget is now created and will be listed under 'Your apps' * You should be redirected to the "Edit app" flow to finish configuring your widget
# Adding the JS Widget to Your Page Source: https://docs.fluz.app/developers/adding-the-js-widget-to-your-page To install the widget, simply drop the following code onto your site. Ensure that the [patToken is generated](/developers/setting-up-your-server) and available to your page ```javascript theme={null} ``` This snippet will load the script from our remote bucket and add it to your page, and the initiate the widget. ## Add a button to an existing container on your page. ```javascript theme={null} ``` ## Bind the widget to an existing button on your page. ```javascript theme={null} ``` # Configure App Widget Source: https://docs.fluz.app/developers/configure-app-widget After you've created a new app widget, you will need to configure the OAuth settings in order for the widget to obtain the necessary permissions from the user. Follow these steps below. * **Navigate to 'Your apps' from the developer dashboard** * **Select the app to set up** * **Review the Overview tab for correctness** * Add an avatar and logomark. * **Review Permissions** * This is the list of individual scopes that will be requested for the user to approve. * Rather than list each scope individually for the user to approve, any scopes that are selected on this page will be grouped under the top level header, and the individual header will be presented to the user. If a scope is left unchecked, it will also be omitted from what the user ultimately approves. * Some scopes that are required for the particular widget type you are configuring to be able to run will be gathered at the bottom of this tab. The user will be unable to unselect these specific scopes. * **Provide the following settings under OAuth:** * **Origin** - The domain name that will be hosting the widget, for example google.com or mysite.com. * **Redirect URIs** - A public URL that our authorization server can redirect to for the OAuth authorization code flow. Do not include any query parameters in this URL. You can include any number of these URIs. If you use the OAuth flow to generate an authorization code, you must use the same callback URI for the exchange of the code. * **Webhook URLs** - A public REST endpoint to receive widget related events from Fluz. * You can add as many webhook URLs as you would like, and you can subscribe the individual URLs to specific webhook events. If you provide a URL without any webhook events selected, then it will be treated as a "catch-all" URL and all webhook events will be sent to that URL. * **Review the 'Installation' section.** This will include the generated Javascript code for your widget. You can copy and embed this Javascript code into your web application to host the widget. # Disable or Delete Your App Source: https://docs.fluz.app/developers/disable-or-delete-your-app If you would like to disable or delete your widget. Follow the steps below. * Navigate to 'Your apps' in the developer dashboard * Select the app * There are two buttons at the bottom of every tab: 'Disable app' and 'Delete app' * To disable, select 'Disable app'. This will set the app status to 'DISABLED', and users should be unable to interact with your app. You can reenable your app later. * To delete, select 'Delete app'. # Security & compliance Source: https://docs.fluz.app/developers/security How Fluz protects funds, customer data, and card credentials — and what that means for your integration. Fluz is built to keep you out of scope for the parts of financial infrastructure that are hardest to get right. ## Certifications * **SOC 2 Type II** — audited annually. * **PCI DSS Level 1** — the highest tier of card-data compliance. * **FDIC-insured banking partners** — balances are held at insured US institutions. ## Data protection * TLS 1.2+ required for every request; older versions rejected at the load balancer. * Data encrypted at rest with AES-256. * PANs, CVVs, and PINs never appear in your logs — they're only accessible through PCI-compliant [widgets](/developers/widgets). * Secrets rotated automatically; API keys and OAuth secrets can be rotated on demand from the dashboard. ## Fraud monitoring * 24/7 fraud operations team monitoring transactions in real time. * ML-based scoring on every authorization. * Automatic velocity checks; you can add your own rules via the dashboard. * Optional manual review holds for high-risk flows. ## Your responsibilities 1. **Never ship your API Key or OAuth `clientSecret` values to a client.** Mint access tokens on your server. 2. **Verify webhook signatures** before trusting a payload. See [Configure App Widget](/developers/configure-app-widget) for webhook URL setup. 3. **Scope tokens narrowly.** Only request the scopes you actually use — mint a new token when you need broader access. 4. **Persist your own operation IDs** so retries land safely under de-duplication (see [Idempotency](/concepts/idempotency)). 5. **Rotate application secrets** on employee turnover and after any suspected exposure. ## Reporting a vulnerability Email `humans@fluz.app` with reproduction steps. We acknowledge within one business day and coordinate disclosure timelines with reporters. # Set Up Your Server to Interact With The Fluz JS Widget Source: https://docs.fluz.app/developers/setting-up-your-server Installation of the Fluz Javascript widget is relatively straightforward. You must embed or import the Javascript onto a page on your site. Once the javascript has loaded in, you can bind a UI element to open the javascript modal included as part of the imported javascript. You must also include a JWT that pre-authorizes the user to complete a particular transaction, as described below. ## Create a "Pre-approved transaction" token. Since the user is interacting with the Fluz platform to move money in and out of your spend account, any interaction with the widget must include a signed JWT token that effectively "pre-authorizes" the user to complete the transaction. What this means is, for instance, if user "abc123" has \$50 in their operator account that they wish to withdraw to Fluz, you generate a `patToken` that Fluz can use to validate the transaction and amount. This patToken contains the `amount`, your `apiKey`, the `transactionType`, the `externalId`, user data, and then a JTI. This token is signed with your `apiSecret` that can be found in your app details in the `for-developers` section of Fluz. | key | value | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | amount | the amount in dollars and cents that the user is approved to transact with | | transactionType | `DEPOSIT` or `WITHDRAW`.
`DEPOSIT` means the user is putting money into Fluz, and then moving it to your operator spend account.
`WITHDRAW` means the user is doing a payout from your operator spend account to their Fluz account. | | apiKey | the API key of your app | | externalId | Your unique identifier for your user. Can be a userId, accountId, phone number, email address - whatever you use in your system. We will provide this when we send you information like webhook events, so you can map the user in your system. | | jti | a UUID v4 that is generated when you go to sign the token. We use this token for idempotency. You can read more [in the official jti spec](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.7). | | phoneNumber (optional) | a string with phone number of user. If passed, user will skip the **Enter the phone number** step of login/registration and will be moved straight to **Enter the two-factor-authentication code** step | | firstName (optional) | a string with the first name of the user | | lastName (optional) | a string with the last name of the user | | email (optional) | a string with the email of user | | username (optional) | a string with the username | ## Code snippets to generate the JWT ### JavaScript ```javascript theme={null} import jwt from 'jsonwebtoken'; const { sign } = jwt; const amount = 0; // Pass in the amount here const transactionType = ""; // DEPOSIT or WITHDRAW const externalId = ""; // This is your unique identifier. It might be a userID, accountID. const jti = uuidv4(); const secret = ""; // Your api secret const generatedToken = sign({ amount, apiKey: "", // Your apiKey transactionType, externalId, jti, }, secret, { expiresIn: '1 day' }); ``` ### Ruby Installation: `gem install jwt` ```ruby theme={null} require 'jwt' require 'securerandom' amount = 0 # Pass in the amount here transaction_type = "" # DEPOSIT or WITHDRAW external_id = "" # This is your unique identifier. It might be a userID, accountID. jti = SecureRandom.uuid secret = "" # Your api secret payload = { amount: amount, apiKey: "", # Your apiKey transactionType: transaction_type, externalId: external_id, jti: jti, exp: Time.now.to_i + (24 * 60 * 60) # 1 day } generated_token = JWT.encode(payload, secret, 'HS256') puts generated_token ``` ### Python Installation: `pip install PyJWT` ```python theme={null} import jwt import uuid from datetime import datetime, timedelta amount = 0 # Pass in the amount here transaction_type = "" # DEPOSIT or WITHDRAW external_id = "" # This is your unique identifier. It might be a userID, accountID. jti = str(uuid.uuid4()) secret = "" # Your API_SECRET. Please reach out to your Fluz account manager if you do not have this. payload = { "amount": amount, "apiKey": "", # Your apiKey "transactionType": transaction_type, "externalId": external_id, "jti": jti, "exp": datetime.utcnow() + timedelta(days=1) } generated_token = jwt.encode(payload, secret, algorithm="HS256") print(generated_token) ``` ### Go ```go theme={null} package main import ( "fmt" "time" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) func main() { amount := 0 // Pass in the amount here transactionType := "" // DEPOSIT or WITHDRAW externalId := "" // This is your unique identifier. It might be a userID, accountID. jti := uuid.New().String() secret := "" // Your api_secret claims := jwt.MapClaims{ "amount": amount, "apiKey": "", // your api_key "transactionType": transactionType, "externalId": externalId, "jti": jti, "exp": time.Now().Add(24 * time.Hour).Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) generatedToken, err := token.SignedString([]byte(secret)) if err != nil { fmt.Println("Error generating token:", err) return } fmt.Println(generatedToken) } ``` ### Java ```java theme={null} import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class JwtGenerator { public static void main(String[] args) { int amount = 0; // Pass in the amount here String transactionType = ""; // DEPOSIT or WITHDRAW String externalId = ""; // This is your unique identifier. It might be a userID, accountID. String jti = UUID.randomUUID().toString(); String secret = ""; // Your api_secret Map claims = new HashMap<>(); claims.put("amount", amount); claims.put("apiKey", ""); // your api_key claims.put("transactionType", transactionType); claims.put("externalId", externalId); long expirationTime = System.currentTimeMillis() + (24 * 60 * 60 * 1000); // 1 day String generatedToken = Jwts.builder() .setClaims(claims) .setId(jti) .setExpiration(new Date(expirationTime)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); System.out.println(generatedToken); } } ``` ### PHP ```php theme={null} toString(); $secret = ""; // Your api_secret $payload = [ "amount" => $amount, "apiKey" => "", // your api_key "transactionType" => $transactionType, "externalId" => $externalId, "jti" => $jti, "exp" => time() + (24 * 60 * 60) // 1 day ]; $generatedToken = JWT::encode($payload, $secret, 'HS256'); echo $generatedToken; ?> ``` ### C# (.NET) ```csharp theme={null} using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Microsoft.IdentityModel.Tokens; using System.Text; class JwtGenerator { static void Main() { int amount = 0; // Pass in the amount here string transactionType = ""; // DEPOSIT or WITHDRAW string externalId = ""; // This is your unique identifier. It might be a userID, accountID. string jti = Guid.NewGuid().ToString(); string secret = ""; // Your api_secret var claims = new List { new Claim("amount", amount.ToString()), new Claim("apiKey", ""), // your api_key new Claim("transactionType", transactionType), new Claim("externalId", externalId), new Claim(JwtRegisteredClaimNames.Jti, jti) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( claims: claims, expires: DateTime.UtcNow.AddDays(1), signingCredentials: creds ); var generatedToken = new JwtSecurityTokenHandler().WriteToken(token); Console.WriteLine(generatedToken); } } ``` # Embedded Widgets Overview Source: https://docs.fluz.app/developers/widgets Drop a hosted Fluz flow into your own product to collect user permissions, capture sensitive data inside our PCI scope, and confirm money movement — then run everything else server-side over the API. ## What a widget actually is A Fluz Widget is a hosted, Fluz-rendered flow that you embed in your own site with a few lines of JavaScript. It runs in a modal on top of your page, on your domain, under your branding. It exists to do three jobs that you should not have to build yourself: The widget is how an end user creates or logs into their Fluz account and **grants your application the scopes it needs to act on that account**. No grant, no API access. Card numbers, SSNs, identity documents, and PINs are collected by Fluz, inside Fluz's PCI DSS environment, and encrypted on our side. They never touch your servers. The user sees and approves the amount and direction of a transfer in a surface they can trust, which is what turns a pre-authorized token into a completed transaction. [Demo a widget: payouts](https://demos.fluz.app/payout-prepaid/?launch-card=1\&subtitle=Send%20a%20payout%20and%20watch%20it%20become%20a%20spendable%20card.\&image=https%3A%2F%2Fdemos.fluz.app%2Fassets%2Fprepaid-demo-shot.png\&image-dark=https%3A%2F%2Fdemos.fluz.app%2Fassets%2Fprepaid-demo-shot-dark.png) Everything else — issuing the card, pulling the funds, checking the balance, reading transactions — is yours to do over the API, on your own schedule, with no user present. **The mental model:** the widget is a *consent and sensitive-data surface*, not a product. It is the narrow, high-compliance part of the flow. The API is where the work happens. *** ## The division of labor | Job | Widget | API | | :------------------------------------------ | :----- | :------------------------------------------------------------------- | | Create the user's Fluz account | ✅ | ✅ [User Registration](/docs/user-registration) | | Verify identity (KYC) | ✅ | ✅ [User KYC Verification](/docs/user-kyc-verification) | | Verify a business (KYB) | — | ✅ [Business Registration](/docs/business-registration) | | Obtain scope grants from the user | ✅ | ✅ [OAuth grant flow](/docs/grant-widget-user-permissions-todo) | | Collect a card PAN / CVV | ✅ | Tokenized only | | Collect an SSN or ID document | ✅ | Pass-through if you already hold it | | Set a transaction PIN | ✅ | — | | Confirm a specific transfer amount | ✅ | — | | Link a bank account via Plaid | ✅ | ✅ [Funding Sources](/features/funding-sources) | | Issue, edit, lock, or reveal a virtual card | — | ✅ [Virtual Cards](/features/virtual-cards) | | Deposit, withdraw, transfer, send money | — | ✅ [Wallets & Transfers](/features/move-funds-with-external-accounts) | | Buy gift cards, read the catalog | — | ✅ [Merchant Catalog](/merchant-catalog) | | Read transactions, annotate, run approvals | — | ✅ [Transactions](/features/get-all-transactions) | | Bulk-issue up to 10,000 cards | — | ✅ [Bulk Operations](/features/virtual-cards) | *** ## You can run everything behind the scenes This is the most commonly missed point about the widget: **it is not the only way to use Fluz, and it is not the way most work gets done.** Once a user has granted your application scopes — whether through the widget or through the standalone [OAuth grant flow](/docs/grant-widget-user-permissions-todo) — your server holds a user access token. From that point, every capability listed on the [API Features](/features) page is available to you programmatically, with no widget open and no user watching: Link bank cards and Plaid bank accounts, then pull funds on demand. Open spend accounts, deposit, withdraw, and move funds internally or across accounts. Spend controls, lock/unlock, PINs, wallet provisioning, bulk issuance. Generate hosted card links recipients claim, with full link lifecycle control. Look up recipients by phone or email and transfer to other Fluz wallets. Add team members, issue them cards, and route approval requests. The widget's job is to get you to the token. What you do after that is entirely server-side. *** ## Pick how much of the flow you hand to us You do not have to choose "all widget" or "all API." Most integrations land somewhere in the middle, and the deciding factor is usually **what sensitive data you already hold and want to keep holding.** **You hand us the whole user journey.** The widget handles account creation, phone + 2FA login, KYC, PIN setup, the permissions grant, and the transaction confirmation. You render a button and generate a signed token. * Fastest path to production — measured in hours, not sprints. * Zero PCI scope, zero CIP data handling on your side. * Least control over look and feel between the click and the callback. **Good fit:** payout and withdrawal flows, marketplaces, gig platforms, rewards programs — anywhere you want money to leave your system without you becoming a financial institution. **You own the parts you already own; we own the parts you'd rather not.** Register the user yourself with [`registerUser`](/docs/user-registration) using the profile data you already collected at signup. Run KYC yourself with [`verifyUserInformation`](/docs/user-kyc-verification) if you already hold the SSN and address. Then open the widget only for the steps that genuinely need it: * the permissions grant, * document upload when KYC comes back `DECLINED` or needs review, * card PAN capture, * PIN setup, * the transaction confirmation screen. Your onboarding stays yours. The user never re-types information you already have. The widget appears for a narrow, obviously-financial moment and then gets out of the way. **Good fit:** platforms with an existing KYC'd user base, fintechs, anyone who has already done identity verification and does not want to make the user do it twice. **No widget at all.** Register users, verify them, link funding sources, issue cards, and move money entirely over the API. Obtain scope grants through the standalone [OAuth authorization flow](/docs/grant-widget-user-permissions-todo) — a redirect, not an embed — or operate on your own platform account. * Full control over every pixel. * **You** are responsible for PCI DSS scope if you collect card data, and for the security of any CIP data you handle. * Some flows still require a hosted surface: revealing full card details to an end user and collecting identity documents are the usual holdouts. **Good fit:** bulk issuance, back-office operations, disbursement runs, ERP and accounting integrations, and any flow with no end user in the loop. **On registering users via API:** if you register and KYC a user yourself and *then* open the widget, pass `externalId` in the pre-approved transaction token so we can match the session to the account you already created rather than starting a new one. You can also pass `phoneNumber`, `firstName`, `lastName`, `email`, and `username` to skip the corresponding steps in the widget. See [Set Up Your Server](/developers/setting-up-your-server). *** ## How widgets relate to OAuth applications A widget **is** an OAuth application. It is not a separate object with a separate permission model — it is an OAuth app that ships with an embeddable front end. On the **Permissions** tab of your app, you select the scopes your application is allowed to request. This is the maximum your app can ever ask for, regardless of what any individual user agrees to. Scopes that a given widget type cannot function without are grouped at the bottom of the tab and cannot be unselected. See [Application Scopes](/docs/application-scopes) for the full list — `MAKE_DEPOSIT`, `MAKE_WITHDRAW`, `LIST_PAYMENT`, `CREATE_VIRTUALCARD`, `REVEAL_VIRTUALCARD`, `PURCHASE_GIFTCARD`, and the rest. **Origin** — the domain hosting the widget. **Redirect URIs** — where our authorization server may send the user back, no query parameters, and it must match exactly at exchange time. **Webhook URLs** — one or many REST endpoints, each optionally subscribed to specific events; a URL with no events selected becomes a catch-all. See [Configure App Widget](/developers/configure-app-widget). When the widget opens, the user is shown the scopes you requested — grouped under readable top-level headers rather than listed as raw enum values — and approves them. Anything they decline is simply not granted. An application's effective permissions are the **intersection** of the app-level grant and the user-level grant, and both must be unexpired. This is enforced at `generateUserAccessToken`, not at call time — so a revoked or lapsed grant surfaces as a token failure, not a mysterious mid-flow error. The grant produces an authorization `code` at your redirect URI. Exchange it at `/token/exchange` with a Basic auth header of `client_id:client_secret` for an `accessToken`, a `refreshToken`, and the confirmed `scope` array. See [Exchanging an authorization code](/docs/exchanging-an-oauth-authorization-code) and [Refreshing an access token](/docs/refreshing-an-oauth-access-token). The pre-approved transaction token (`patToken`) and the OAuth access token are **different things** and do different jobs. The `patToken` is a short-lived, single-transaction JWT signed with your `apiSecret` that authorizes *one* movement of *one* amount. The OAuth `accessToken` is what lets your server act on a user's account over time. A widget session typically involves both. *** ## PCI compliance and sensitive data When the widget is open, the sensitive fields inside it are Fluz's, not yours. The user is typing into our iframe, posting to our servers, under our compliance program. That means Fluz takes responsibility for: * **Card data.** PANs, expiration dates, and CVVs are captured and stored in accordance with PCI DSS requirements and encrypted at rest on our side. Your page never sees them, your logs never contain them, and your infrastructure stays out of PCI scope for these flows. * **Full card reveal.** Showing an end user their own virtual card number is a hosted Fluz surface for the same reason. * **CIP and identity data.** SSNs, dates of birth, addresses, and uploaded identity documents are collected and retained inside our verification environment. * **PINs.** Set and stored by us, never transmitted to you. * **Bank credentials.** Plaid link flows run inside the widget; you never handle the user's banking login. What stays your responsibility: your `apiSecret` and `client_secret`. The Installation tab renders working snippets that contain your real credentials, which is convenient and also a hazard — **generate the `patToken` on your server, never in browser JavaScript.** Anything in your page source is public. Fluz maintains SOC 2 Type II controls and handles card data in accordance with PCI DSS requirements. If your compliance team needs documentation for a vendor review, contact your Fluz account manager. *** ## Getting your embed code You do not hand-write the integration. The **Installation** tab of your app generates it for you, pre-filled with your app's real `apiKey`, and gives you two selectors: **Transaction Type** — choose the direction of money movement: | Installation tab label | `transactionType` in the JWT | What happens | | :--------------------- | :--------------------------- | :---------------------------------------------------------------------------------------------------------- | | **Pay-In** | `DEPOSIT` | The user moves funds into Fluz and on into your operator spend account. Money flows *toward* your platform. | | **Payout** | `WITHDRAW` | Funds move from your operator spend account to the user's Fluz account. Money flows *toward* your user. | **Server Language** — the snippet that generates the signed pre-approved transaction token, in the language your backend actually uses: Switch the selector and the code block rewrites itself — correct JWT library, correct claim names, correct HS256 signing, correct one-day expiry. Copy it, drop in your `apiSecret` from your secret store, and you have a working token generator. Every variant is also documented in full at [Set Up Your Server](/developers/setting-up-your-server). The client-side half is a single script tag plus a `FluzEmbedded.init(...)` call. You can let us render the button, or bind the modal to a button you already have. See [Adding the JS Widget to Your Page](/developers/adding-the-js-widget-to-your-page). Your app's configuration lives at: ```text theme={null} https://fluz.app/for-developers/overview/{appId} ``` for example `https://fluz.app/for-developers/overview/19be9561-a6a1-4e02-8243-10ede908ef33`. The tabs across the top — **Overview**, **Permissions**, **OAuth**, **Installation** — map exactly to the steps above. *** ## Start from a template You do not start from a blank app. From the developer dashboard, choose **Browse templates** and pick the one closest to what you are building. A template pre-configures the app type, the required scopes, the transaction direction, and the sequence of screens the user will see — so a new app is functional the moment you finish naming it. Templates available today include: | Template | What it sets up | | :----------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | **Withdraw to a virtual Mastercard** | A `Payout` flow ending in an instantly usable Fluz virtual card. Pre-selects the withdrawal and card-creation scopes. | | **OAuth Integration** | A permissions-only app with no embedded UI — for headless and redirect-based integrations. | **Treat the template as a starting point, not a specification.** After it is created, go to the **Permissions** tab and shape the app around what you are actually trying to do — add the scopes your use case needs, remove the ones it does not. A payout widget that will later issue cards on the user's behalf needs `CREATE_VIRTUALCARD`; one that only moves cash does not. Requesting fewer scopes means a shorter consent screen and a higher completion rate, so ask for what you need and nothing more. Creating an app: [Add a New App Widget](/developers/add-a-widget) · Configuring it: [Configure App Widget](/developers/configure-app-widget) · Turning it off: [Disable or Delete Your App](/developers/disable-or-delete-your-app) *** ## What the end user sees Once a user reaches a page hosting your widget and takes the action that opens the modal: The user authenticates to their Fluz account with a 2FA code sent to their phone. If they do not have an account, they create one here. Passing `phoneNumber` in the `patToken` skips straight to the code entry step. If you already hold the user's SSN, pass it to us and we validate it. If not, the widget runs the full KYC flow. Responses are `APPROVED`, `DECLINED`, `DUPLICATE`, or `ERROR` — see [User KYC Verification](/docs/user-kyc-verification) for what each means and how many attempts a user gets. The user reviews and approves the scopes your app requested. A Fluz-wide security measure, prompted again later for actions requiring elevated confirmation. The user sees the amount and direction and either approves or dismisses. Either way, you get an event. ### Pay-In: funds into your platform Check the user's Fluz balance first to confirm they can cover the transaction. 1. The user enters a deposit amount and clicks your button. 2. The widget presents a confirmation screen. * **Confirmed** → we initiate the transfer from the user's spend account to yours. * **Denied or dismissed** → we send an event. 3. You receive a completion or failure event. 4. Verify your own Fluz balance to confirm settlement. ### Payout: funds out to your user Check your account's Fluz balance first. If you cannot cover the transfer, initiate a deposit from your funding source. Quarantine or hold the user's funds on your side to prevent double-spend while the transfer is in flight. 1. The user enters a withdrawal amount and clicks your button. 2. The widget presents a confirmation screen. * **Confirmed** → we initiate the transfer from your operator spend account to the user's. * **Denied or dismissed** → we send an event. 3. You receive a completion or failure event. 4. The widget shows the user their withdrawal is complete and gives them direct access to their Fluz virtual card. Every money-moving call needs a unique `jti` in the token for idempotency, and a unique `idempotencyKey` on the API side. See [Idempotency](/docs/idempotency-requests). *** ## Next steps Create your first app from a template. Scopes, origins, redirect URIs, webhooks. Generate the pre-approved transaction token in your language. Script tag, init call, button binding. The full capability surface, all of it available server-side. Run every capability on connected accounts with customer-scoped tokens. # Discover Connected Users Source: https://docs.fluz.app/discover-connected-users Return the users connected to your application through active OAuth grants, with their external reference id and granted scopes. Returns the users connected to your application through active OAuth grants, with the `externalReferenceId` and granted scopes for each. Use it to see who you can target and what each user has authorized before calling other bulk operations. Returns identifiers and granted scopes only — **never user tokens**. ## Requirements * `Authorization: Basic ` (your application API key) * The **bulk API capability** on your application ## Query ```graphql theme={null} query GetBulkConnectedOAuthUsers($paginate: BulkPaginationInput) { getBulkConnectedOAuthUsers(paginate: $paginate) { totalCount hasNextPage users { externalReferenceId accountId userId scopes connectedAt } } } ``` ### Variables ```json theme={null} { "paginate": { "limit": 100, "offset": 0 } } ``` ## Response ```json theme={null} { "data": { "getBulkConnectedOAuthUsers": { "totalCount": 128, "hasNextPage": true, "users": [ { "externalReferenceId": "your-user-001", "accountId": "8f3c…", "userId": "b21a…", "scopes": ["LIST_PAYMENT", "LIST_PURCHASES"], "connectedAt": "2026-05-01T12:00:00.000Z" } ] } } } ``` ## Arguments | Argument | Type | Description | | ----------------- | ----- | ------------------------------------------------ | | `paginate.limit` | `Int` | Users per page. Maximum and default are **100**. | | `paginate.offset` | `Int` | Number of users to skip. Defaults to `0`. | ## Response fields | Field | Description | | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `users[].externalReferenceId` | Your reference id for the user. `null` if none was set at connect time — such users are only reachable via `ALL_CONNECTED`. | | `users[].accountId` | The connected user's account id. | | `users[].userId` | The connected user's user id. | | `users[].scopes` | Scopes the user granted to your application. | | `users[].connectedAt` | When the user connected. | | `totalCount` | Total connected users across all pages. | | `hasNextPage` | Whether more users exist beyond this page. | ## Notes * The bulk API allows larger pages than the rest of the API — up to 100 per page. * Page with `offset` until `hasNextPage` is `false`. # Exchange an OAuth Code Source: https://docs.fluz.app/exchange-an-o-auth-authorization-code ## Exchanging an auth code for an Access Token You can take the code received as the response to a permissions request and make a request to exchange for an Access Token and Refresh Token. To excxhange your authorization `code` for an access token, make a request to `https://uni.staging.fluzapp.com/token/exchange` with the following query params: | query param | description | | ------------- | ---------------------------------------------------------------------------------- | | code | The auth token above | | redirect\_uri | the redirect\_uri you used in the previous auth step. This uri must match exactly. | Additionally, set an Authorization header that is a base64 encoded string that is a combination of your client\_id:app\_secret. This is a Basic auth header, and so should follow the following format: `Authorization: Basic ` For example, if your `client_id` is `abc123` and your `client_secret` from the OAuth configuration is `def456`, the base64 encoded value would be `YWJjMTIzOmRlZjQ1Ng==`. The `client_id` and `client_secret` can be found in the `Overview` tab of the [Configure OAuth App](/configure-o-auth-app). Here is an example cURL command: ```text theme={null} curl -X GET "https://uni.staging.fluzapp.com/token/exchange?code=&redirect_uri=" -H "Authorization: Basic YWJjMTIzOmRlZjQ1Ng" ``` The response will include: | query param | description | | :----------- | :--------------------------------------------------------------------- | | accessToken | Short-lived token for subsequent requests into Fluz‘s backend services | | refreshToken | refresh token to store and use when the accessToken becomes expired | | scopes | the values the user permitted through the previous flow | Here is an example of a full response: ```json theme={null} {"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTc1MjA5NDgwNH0.0dKCtVpN0mTHHMuGNNx4VLJisTnovFNPQKhSw6zWosc","authorizationCode":"f082972eb110b80f73b0d0f95d1de9266069a1e4","accessTokenExpiresAt":"2025-07-08T21:05:30.673Z","refreshToken":"8ec16c25951616150b0332a4a6d66547","refreshTokenExpiresAt":"2025-08-08T20:55:30.738Z","scope":\["MAKE\_WIDTHDRAW","MAKE\_DEPOSIT","LIST\_PAYMENT"],"client":\{"id":"dab5c80e-0321-4c3a-988a-ffedfd64d8db","app\_id":"0a92d46e-edf4-422e-8c62-946051e5067b","app\_name":"First OAuth integration","grants":\["authorization\_code","client\_credentials","password","refresh\_token"],"redirectUris":\["http\://localhost:3035/oauth/finalize"],"accessTokenLifetime":600},"user":\{"id":"5070d5a1-d71a-4190-91b0-f116eec51771"}} ``` # Create Bulk Order Source: https://docs.fluz.app/features/create-bulk-order The `createVirtualCardBulkOrder` mutation allows you to create multiple virtual cards in a single request. **Prerequisites:** a user access token with the `CREATE_VIRTUALCARD` scope, and a `CreateVirtualCardBulkOrderInput` object. To find an `offerId`, see [Get Virtual Card Offers](/features/get-card-offers). Bulk orders are processed asynchronously. This mutation returns an `orderId` and an initial `orderStatus` of `PENDING` — poll [Get Bulk Order Status](/features/get-bulk-order-status) to track completion. ## Sandbox test offers | Offer ID | Program Name | Reward Value | | :------------------------------------- | :--------------------------------------------- | :----------- | | `ed669305-5e43-40a0-9a25-7a15ed174628` | Virtual Card | 1.5% | | `b23630f6-8d91-43df-84aa-a541e7691197` | Virtual Card - Mastercard Prepaid | 1.5% | | `592c394e-26cc-44ac-a145-a5f81301fe77` | Brand Locked Virtual Card - Mastercard Prepaid | 1.5% | ## Important considerations * **Offer ID:** The `offerId` is applied to all cards created within the bulk order. * **Funding Source:** You can fund your virtual cards using either your Fluz balance or a linked bank account. * If you select a bank account as the `primaryFundingSource`, you must provide the `bankAccountId`. * **Spend Limits:** The `spendLimit` you set must adhere to the program's defined spend limits for the chosen `spendLimitDuration`. An error will occur if the limit is exceeded. * **Quantity:** Each item in the `orderItems` array includes a `quantity`, allowing you to create multiple cards with the same configuration efficiently. ## Arguments * `input` (`CreateVirtualCardBulkOrderInput!`): The input object containing details for the new virtual card bulk order. ## CreateVirtualCardBulkOrderInput fields | Field | Type | Description | Required | | :----------- | :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------- | :------- | | `offerId` | `UUID!` | The Offer ID to be used for all cards in this bulk order. Use getVirtualCardOffers to fetch a list of active offers. | Yes | | `orderItems` | `[CreateVirtualCardBulkOrderItemInput!]!` | An array of objects, each defining a set of virtual cards to be created. | Yes | ### CreateVirtualCardBulkOrderItemInput fields | Field | Type | Description | Required | | :--------------------- | :------------------------------ | :------------------------------------------------------------------------------------------------- | :------- | | `quantity` | `Int!` | The number of virtual cards to create with this specific configuration. | Yes | | `spendLimit` | `Float!` | The maximum amount you can charge to each card. You are only charged for the amount actually used. | Yes | | `spendLimitDuration` | `VirtualCardSpendLimitDuration` | Card limit duration type. Default is `LIFETIME`. | No | | `lockDate` | `String` | The date when the card will be locked. The default is 47 months from creation. Format: yyyy-mm-dd | No | | `lockCardNextUse` | `Boolean` | Setting to lock the card after its next use. The default is `false`. | No | | `cardNickname` | `String` | The card's nickname. | No | | `primaryFundingSource` | `VirtualCardFundingSource` | Primary Funding Source for Virtual Card. Default is `FLUZ_BALANCE`. | No | | `bankAccountId` | `UUID` | The unique identifier for the bank account. Required if `primaryFundingSource` is `BANK_ACCOUNT`. | No | ## cURL example ```bash theme={null} curl -X POST \ https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \ -d '{ "query": "mutation CreateVirtualCardBulkOrder { createVirtualCardBulkOrder( input: { offerId: \"592c394e-26cc-44ac-a145-a5f81301fe77\", orderItems: [ { quantity: 3, spendLimit: 100, spendLimitDuration: DAILY, lockCardNextUse: true, cardNickname: \"Test nickname\", primaryFundingSource: FLUZ_BALANCE }, { quantity: 1, spendLimit: 200, spendLimitDuration: WEEKLY, lockCardNextUse: false, lockDate: \"2030-10-10\", cardNickname: \"Test nickname 2\", primaryFundingSource: BANK_ACCOUNT, bankAccountId: \"7f60cb5d-683f-4181-9194-408ea92d6248\" } ] } ) { orderId orderStatus } }" }' ``` ## Sample mutation ```graphql theme={null} mutation CreateVirtualCardBulkOrder { createVirtualCardBulkOrder( input: { offerId: "592c394e-26cc-44ac-a145-a5f81301fe77" orderItems: [ { quantity: 3 spendLimit: 100 spendLimitDuration: DAILY lockCardNextUse: true cardNickname: "Test nickname" primaryFundingSource: FLUZ_BALANCE } { quantity: 1 spendLimit: 200 spendLimitDuration: WEEKLY lockCardNextUse: false lockDate: "2030-10-10" cardNickname: "Test nickname 2" primaryFundingSource: BANK_ACCOUNT bankAccountId: "7f60cb5d-683f-4181-9194-408ea92d6248" } ] } ) { orderId orderStatus } } ``` ## Sample response ```json theme={null} { "data": { "createVirtualCardBulkOrder": { "orderId": "MTFkYjIxNTYtNTEwOS00ODNmLTkwMTYtYjA5N2E2NTEyMjU5fDIzMjIxODRhLTEzMjgtNDkwYy05Mzc1LTZlOWM4MWIwMjkzZA==", "orderStatus": "PENDING", } } } ``` ## Response fields | Field | Type | Description | | ------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `orderId` | `UUID` | The unique identifier for the bulk creation order. Pass this to `getVirtualCardBulkOrderStatus` to track progress. | | `orderStatus` | `VirtualCardBulkOrderStatus!` | The status of the bulk order (e.g., `COMPLETED`, `PENDING`, `FAILED`). | ## Next steps Poll the `orderId` to track processing and retrieve the cards as they're issued. # Get Bulk Order Status Source: https://docs.fluz.app/features/get-bulk-order-status Use the `getVirtualCardBulkOrderStatus` query to check the status of a bulk virtual card creation order and retrieve the details of the created cards as the order is processed. **Prerequisites:** a user access token with the `CREATE_VIRTUALCARD` scope, and the `orderId` returned by [Create Bulk Order](/features/create-bulk-order). The response includes full card numbers, CVVs, and expiry dates in plaintext. Handle it as sensitive cardholder data — transmit over TLS, never log it, and surface it only to authorized users. ## Arguments * `input` (`GetVirtualCardBulkOrderStatusInput!`): The input object containing the identifier for the bulk order. ## GetVirtualCardBulkOrderStatusInput fields | Field | Type | Description | Required | | :-------- | :-------- | :---------------------------------------------------------------------------------------------------------- | :------- | | `orderId` | `String!` | The unique identifier for the bulk order, which you receive from the `createVirtualCardBulkOrder` mutation. | Yes | ## cURL example ```bash theme={null} curl -X POST \ https://transactional-graph.staging.fluzapp.com/api/v1/graphql \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \ -d '{ "query": "query GetVirtualCardBulkOrderStatus { getVirtualCardBulkOrderStatus( input: { orderId: \"ZTBhYTg2YmQtZWQwYS00OWQxLTk5ZmUtZDBhZGY3ZjYxZGUyfDI0NjMyM2NmLTBkMzEtNGI2YS04NGQ2LTRmNGI2NzMzNmU5Yw==\" } ) { orderStatus orderId virtualCards { cardNumber expiryMMYY cvv cardHolderName virtualCardId billingAddress { city } } successfulCardCreations failedCardCreations totalCards } }" }' ``` ## Sample query ```graphql theme={null} query GetVirtualCardBulkOrderStatus { getVirtualCardBulkOrderStatus( input: { orderId: "<>" } ) { orderStatus orderId virtualCards { cardNumber expiryMMYY cvv cardHolderName virtualCardId billingAddress { city } } successfulCardCreations failedCardCreations totalCards } } ``` ## Sample response ```json theme={null} { "data": { "getVirtualCardBulkOrderStatus": { "orderStatus": "COMPLETED", "orderId": "<>", "virtualCards": [ { "cardNumber": "5111147189413564", "expiryMMYY": "08/29", "cvv": "143", "cardHolderName": "Test", "virtualCardId": "104b50d2-97e5-49ee-b9fd-b42120d68212", "billingAddress": { "city": "San Jose" } }, { "cardNumber": "5111143166431356", "expiryMMYY": "08/29", "cvv": "135", "cardHolderName": "Test", "virtualCardId": "03291f04-8358-44f7-ac58-73a6643ac07f", "billingAddress": { "city": "San Jose" } } ], "successfulCardCreations": 4, "failedCardCreations": 0, "totalCards": 4 } } } ``` ## Response fields | Field | Type | Description | | ------------------------------- | ----------------------------- | -------------------------------------------------------------------------------- | | `orderId` | `String` | The unique identifier for the bulk creation order. | | `orderStatus` | `VirtualCardBulkOrderStatus!` | The status of the bulk order (e.g., `COMPLETED`, `PENDING`, `FAILED`). | | `virtualCards` | `[VirtualCardDetails]` | Full details for each successfully created card. Populated as cards are created. | | `virtualCards[].cardNumber` | `String` | The full 16-digit virtual card number. | | `virtualCards[].expiryMMYY` | `String` | The expiration date of the card in MM/YY format. | | `virtualCards[].cvv` | `String` | The 3-digit security code for the card. | | `virtualCards[].cardHolderName` | `String` | The name assigned to the cardholder. | | `virtualCards[].virtualCardId` | `UUID` | The unique identifier for the virtual card. | | `virtualCards[].billingAddress` | `Object` | The billing address associated with the virtual card. | | `successfulCardCreations` | `Int` | The number of cards successfully created so far. | | `failedCardCreations` | `Int` | The number of cards that failed to be created. | | `totalCards` | `Int` | The total number of cards requested in the order. | Poll this query until `orderStatus` is `COMPLETED` (or `FAILED`). While `PENDING`, `virtualCards` fills in incrementally and `successfulCardCreations` climbs toward `totalCards`. ## Next steps Retrieve sensitive card details securely for a single card. Interpret `failedCardCreations` and handle partial-failure cases. # Request Virtual Card Approval Source: https://docs.fluz.app/features/request-virtual-card-approval `requestVirtualCard` Submit a manager approval request to create a virtual card. When approved, Fluz creates the card using the submitted parameters. ## Scopes | Action | Scope | | ------------------ | ---------------------- | | Create request | `REQUEST_VIRTUAL_CARD` | | List requests | `LIST_APPROVALS` | | Approve or decline | `MANAGE_APPROVALS` | ## Webhook Identifiers | Field | Value | | -------------- | ----------------------- | | `approvalType` | `VIRTUAL_CARD_CREATION` | | `approvalCode` | `600003` | Webhook events: `APPROVAL_CREATE`, `APPROVAL_APPROVE`, `APPROVAL_DECLINE`, and `APPROVAL_HANDLER_ERROR` on execution failure. ## Create a Request ### RequestVirtualCardInput | Field | Type | Required | Description | | -------------------- | ----------------------------- | -------- | ----------------------------------------------------------------------- | | `seatId` | UUID | Yes | Seat associated with the requester | | `offerId` | UUID | Yes | Virtual card offer ID. Use `getVirtualCardOffers` to list active offers | | `purchaseAmount` | Float | Yes | Purchase amount for the card | | `channel` | SourcePlatformChannel | Yes | Request origin channel (for example, `API`) | | `fluzpayAmount` | Float | Yes | Amount to fund from Fluz balance | | `spendLimitDuration` | VirtualCardSpendLimitDuration | Yes | Limit duration: `DAILY`, `WEEKLY`, `MONTHLY`, `ANNUAL`, or `LIFETIME` | | `virtualCardPIN` | String | No | Optional card PIN | | `brandLocked` | Boolean | No | Lock card to a specific merchant brand | | `mccId` | String | No | Merchant category restriction | | `shareWith` | \[String] | No | User IDs to share the card with | | `pinAuthToken` | String | No | PIN authorization token when required | ### Sample Mutation ```graphql theme={null} mutation { requestVirtualCard( input: { seatId: "db11ec6c-c371-4184-ab6a-3dfb2c92c4d3" offerId: "b23630f6-8d91-43df-84aa-a541e7691197" purchaseAmount: 100.00 channel: API fluzpayAmount: 100.00 spendLimitDuration: MONTHLY } ) { success messageId error { code message } } } ``` ### Sample Response ```json theme={null} { "data": { "requestVirtualCard": { "success": true, "messageId": "1234567890" } } } ``` ## Approve a Request Call `approveApprovalRequest` with the `approvalId` from `approvalRequests` or the `APPROVAL_CREATE` webhook. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { approveApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` On approval, Fluz creates the virtual card with the parameters from the original request. ## Decline a Request Call `declineApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { declineApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` No virtual card is created when a request is declined. # Request Virtual Card Limit Change Source: https://docs.fluz.app/features/request-virtual-card-limit-change `requestVirtualCardLimitChange` Submit a manager approval request to change a virtual card spend limit. When approved, Fluz updates the card limit. ## Scopes | Action | Scope | | ------------------ | ----------------------------------- | | Create request | `REQUEST_VIRTUAL_CARD_LIMIT_CHANGE` | | List requests | `LIST_APPROVALS` | | Approve or decline | `MANAGE_APPROVALS` | ## Webhook Identifiers | Field | Value | | -------------- | -------------- | | `approvalType` | `VIRTUAL_CARD` | | `approvalCode` | `600002` | Webhook events: `APPROVAL_CREATE`, `APPROVAL_APPROVE`, `APPROVAL_DECLINE`, and `APPROVAL_HANDLER_ERROR` on execution failure. ## Create a Request ### RequestVirtualCardLimitChangeInput | Field | Type | Required | Description | | --------------- | ----------------------------- | -------- | --------------------------------------------------------------------- | | `virtualCardId` | UUID | Yes | ID of the virtual card to update | | `spendLimit` | Float | Yes | New spend limit. Must be greater than zero | | `limitDuration` | VirtualCardSpendLimitDuration | Yes | Limit duration: `DAILY`, `WEEKLY`, `MONTHLY`, `ANNUAL`, or `LIFETIME` | ### Sample Mutation ```graphql theme={null} mutation { requestVirtualCardLimitChange( input: { virtualCardId: "07df5653-43a8-4532-9881-3ab5857bbe11" spendLimit: 500.00 limitDuration: MONTHLY } ) { success messageId error { code message } } } ``` ### Sample Response ```json theme={null} { "data": { "requestVirtualCardLimitChange": { "success": true, "messageId": "1234567890" } } } ``` ## Approve a Request Call `approveApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { approveApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` On approval, Fluz updates the virtual card spend limit. ## Decline a Request Call `declineApprovalRequest` with the `approvalId`. Requires `MANAGE_APPROVALS`. ```graphql theme={null} mutation { declineApprovalRequest(approvalId: "07df5653-43a8-4532-9881-3ab5857bbe12") { success approvalId action } } ``` No limit change is applied when a request is declined. # Finix Source: https://docs.fluz.app/finix Email sales to request access to our BETA program of Payment Processing API connectors. # Formatting Referral Links Source: https://docs.fluz.app/formatting-referral-links Referral links allow you to invite individuals into your Fluz referral network and receive royalties for those referrals. You can also utilize your referral link if you have an affiliate feed where you direct your users to buy gift cards. You will then earn a commission from Fluz from those referrals who use your link. ## Format a referral URL To format the referral URL you will need the `merchantSlug` and your referral code. To view the `merchantSlug` field, you can use the [getMerchants](/get-catalog) query. If you know the name of the merchant, include that input argument to refine the response. Add the `merchantSlug` field into the URL structure below to form the merchant URL: `https://fluz.app/store/{merchant-slug}` Next, you will need your referral code. You can find your referral code on the homepage in the Fluz web portal under **Maximize**. Click on **Invite a friend** to access your referral code. Invite a friend Then, under **Share your referral code** select **Copy** to save your code. Referral code Once you have your referral code, you can add that information to the merchant URL from above to create your referral code URL: `https://fluz.app/store/{merchant-slug}?referred_by={Referral_Code}` ## Using getReferralUrl You can also run the `getReferralUrl` query to automatically display your referral URL link. This query takes the `MerchantInput` as the optional input field. The order of precedence is `id` -> `slug` -> `name`. ```text theme={null} { "id": "f37cff96-6ca7-4197-b4ff-32ce94433455", "slug": "xyz789", "name": "xyz789" } ``` Your referrals can then use this URL to pull the cashback rates from a specific merchant. If no `MerchantInput` is entered or the slug does not exists, it will return the default string: `https://fluz.app/referred-by/{Referral_Code}` Otherwise, the response will be: `https://fluz.app/store/{merchant-slug}?referred_by={Referral_Code}` The full catalog updates twice daily, and stock availability may change. Review [Inventory on Stocked Offers](/get-inventory) to learn how to retrieve stock information. To learn more about the merchant catalog, visit [Catalog Overview](/merchant-catalog). # Get Bulk Wallet Balances Source: https://docs.fluz.app/get-bulk-balances Return cash balances across up to 100 connected users in one call, one result per target. Returns cash balances across up to **100 connected users** in one call, one result per target. ## Requirements * `Authorization: Basic ` (your application API key) * The **bulk API capability** on your application * The `LIST_PAYMENT` scope on each target user's grant Failures are reported per target and never fail the whole request. See [Bulk API Overview](/bulk-api) for the targeting model and error codes. ## Query ```graphql theme={null} query GetBulkBalances($targetSpec: BulkTargetSpecInput!) { getBulkBalances(targetSpec: $targetSpec) { targetCount successCount failureCount results { externalReferenceId accountId success error { code message } balances { userCashBalanceId nickname status totalCashBalance availableCashBalance lifetimeCashBalance createdAt } } } } ``` ### Variables — selected users ```json theme={null} { "targetSpec": { "mode": "SELECTED", "targets": ["your-user-001", "your-user-002"] } } ``` ### Variables — all connected users ```json theme={null} { "targetSpec": { "mode": "ALL_CONNECTED" } } ``` ## Response ```json theme={null} { "data": { "getBulkBalances": { "targetCount": 2, "successCount": 1, "failureCount": 1, "results": [ { "externalReferenceId": "your-user-001", "accountId": "8f3c…", "success": true, "error": null, "balances": [ { "userCashBalanceId": "ucb-1", "nickname": "Main account", "status": "ACTIVE", "totalCashBalance": "125.50", "availableCashBalance": "100.00", "lifetimeCashBalance": "500.00", "createdAt": "2026-01-01T00:00:00.000Z" } ] }, { "externalReferenceId": "your-user-002", "accountId": "a90d…", "success": false, "error": { "code": "INSUFFICIENT_SCOPE", "message": "The user has not granted the scopes this operation requires. Missing scopes: LIST_PAYMENT." }, "balances": null } ] } } } ``` ## Arguments | Argument | Type | Description | | -------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `targetSpec.mode` | `BulkTargetMode!` | `ALL_CONNECTED` or `SELECTED`. | | `targetSpec.targets` | `[String!]` | `externalReferenceId`s to target. Required for `SELECTED`; ignored for `ALL_CONNECTED`. Duplicates removed; result order follows this list. Max 100. | ## Response fields | Field | Description | | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `results[].success` | Whether balances were returned for this target. | | `results[].error` | Present when `success` is `false`. See error codes in the [Overview](/bulk-api). | | `results[].balances` | The user's cash balances. Restricted to permitted spend accounts when the grant is spend-account-scoped. | | `targetCount` / `successCount` / `failureCount` | Request-level summary. | A target whose grant is scoped to specific spend accounts returns only those balances — automatically, with no extra parameters. # Get Bulk Transactions Source: https://docs.fluz.app/get-bulk-transactions Fetch transactions across your connected users as a single flat, keyset-paginated list. Returns transactions across your connected users as a **single flat, keyset-paginated list**, newest first. Every row carries the `externalReferenceId` and `accountId` of the user it belongs to. There is **no per-user cap** — page with `nextCursor` to walk every transaction in the window across every target user. Defaults to the **last 90 days** when no date filter is supplied. **Breaking change.** This query previously returned `results`, one entry per target user, each holding up to 20 transactions. It now returns a flat `transactions` list with cursor pagination. See [Migrating from the per-target response](#migrating-from-the-per-target-response). ## Requirements * `Authorization: Basic ` (your application API key) * The **bulk API capability** on your application * The `LIST_PAYMENT` and `LIST_PURCHASES` scopes on each target user's grant Failures are reported per target and never fail the whole request. See [Bulk API Overview](/bulk-api) for the targeting model and error codes. ## Query ```graphql theme={null} query GetBulkTransactions( $targetSpec: BulkTargetSpecInput! $createdGte: DateTime $createdLte: DateTime $includeMetadata: Boolean $limit: Int $after: String $includeTotalCount: Boolean ) { getBulkTransactions( targetSpec: $targetSpec createdGte: $createdGte createdLte: $createdLte includeMetadata: $includeMetadata limit: $limit after: $after includeTotalCount: $includeTotalCount ) { transactions { externalReferenceId accountId recordId transactionType amount status createdAt memo transactionCategory } errors { externalReferenceId accountId code message } totalCount targetCount succeededTargetCount failedTargetCount nextCursor hasMore } } ``` ### Variables ```json theme={null} { "targetSpec": { "mode": "SELECTED", "targets": ["your-user-001", "your-user-002"] }, "createdGte": null, "createdLte": null, "includeMetadata": false, "limit": 100, "after": null, "includeTotalCount": true } ``` ## Response ```json theme={null} { "data": { "getBulkTransactions": { "transactions": [ { "externalReferenceId": "your-user-001", "accountId": "8f3c…", "recordId": "f85b1ce1-1766-4af5-b539-7f3fab4db33d", "transactionType": "Account Transfer - In", "amount": 25.0, "status": "SETTLED", "createdAt": "2026-07-01T00:00:00.000Z", "memo": null, "transactionCategory": null }, { "externalReferenceId": "your-user-001", "accountId": "8f3c…", "recordId": "ede29c85-6d9c-4a7e-b2be-9b6ec7b93cb5", "transactionType": "Add Money", "amount": 100.0, "status": "SETTLED", "createdAt": "2026-06-30T18:12:04.000Z", "memo": null, "transactionCategory": null } ], "errors": [ { "externalReferenceId": "your-user-002", "accountId": null, "code": "TARGET_NOT_CONNECTED", "message": "The id does not correspond to a user currently connected to your application." } ], "totalCount": 1284, "targetCount": 2, "succeededTargetCount": 1, "failedTargetCount": 1, "nextCursor": "eyJjcmVhdGVkQXQiOiIyMDI2LTA2…", "hasMore": true } } } ``` ## Arguments | Argument | Type | Description | | ------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `targetSpec` | `BulkTargetSpecInput!` | Target selection. See [Targeting](#targeting). | | `createdGte` | `DateTime` | Only include transactions created at/after this time. Defaults to 90 days before `createdLte`. Must not be after `createdLte`. | | `createdLte` | `DateTime` | Only include transactions created at/before this time. Defaults to now. | | `includeMetadata` | `Boolean` | Also return `memo` and `transactionCategory`. Slower; defaults to `false`. | | `limit` | `Int` | Maximum transactions in this page. Default and maximum are both `100`. Larger values are clamped, not rejected. | | `after` | `String` | Opaque cursor from a previous response's `nextCursor`. Omit for the first page. | | `includeTotalCount` | `Boolean` | Also return `totalCount`. Costs a second scan of the window, so it defaults to `false`. Prefer `hasMore`/`nextCursor` to drive paging. | The date window bounds the data; `limit` bounds a single page. **The window may span at most 365 days.** Transactions are stored in monthly partitions and each partition in the window is scanned per target, so a wider window costs more per page — it isn't free. For longer history, use the export. ### Targeting `targetSpec.mode` is either `SELECTED` (name the users in `targets`, using the `externalReferenceId` you connected them with) or `ALL_CONNECTED` (every user with an active grant on your application). Duplicate `targets` are removed. This query accepts up to **1,000 target users**. For more than that, use the asynchronous export. ## Response fields | Field | Type | Description | | ---------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `transactions` | `[BulkTransaction!]!` | This page of transactions across all target users, newest first, each attributed to its user. | | `errors` | `[BulkTargetFailure!]!` | Targets that were skipped or failed. **First page only** (empty on subsequent pages), and capped at a **sample of 100** so a large sweep can't return a huge payload. Use `failedTargetCount` for the true total. | | `totalCount` | `Int` | Total transactions matching the request across **all pages** — every target, whole window. Opt-in via `includeTotalCount`, and only ever computed on the first page; `null` otherwise. It costs a second scan of the window, so ask for it only when you need to show a total. | | `targetCount` | `Int!` | Number of target users addressed by the request. | | `succeededTargetCount` | `Int!` | Number of target users this request was authorized to read — resolved, and holding the required scopes. Independent of this page's contents: an authorized user with no transactions in the window still counts. | | `failedTargetCount` | `Int!` | Number of target users that could not be read. Returned on **every** page (unlike `errors`), and the true total even when `errors` is truncated to its 100-item sample. | | `nextCursor` | `String` | Opaque cursor for the next page; `null` when this is the last page. Keyset-based, so it's stable across concurrent writes. | | `hasMore` | `Boolean!` | Whether more transactions remain beyond this page. | ### `BulkTransaction` | Field | Type | Description | | --------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `externalReferenceId` | `String` | Your reference id for the user this transaction belongs to. | | `accountId` | `UUID!` | The Fluz account id of the user this transaction belongs to. | | `recordId` | `UUID` | Unique identifier for the transaction record. | | `transactionType` | `String` | Display-style label, e.g. `"Account Transfer - In"`, `"Account Transfer - Out"`, `"Spend Account Transfer - In"`, `"Add Money"`. Not an enum — see the note below. | | `amount` | `Float!` | The transaction amount. | | `status` | `TransactionStatus` | `PENDING` or `SETTLED`. | | `createdAt` | `DateTime` | When the transaction was created. | | `memo` | `String` | Populated only when `includeMetadata: true`. | | `transactionCategory` | `String` | Populated only when `includeMetadata: true`. | Four behaviors to be aware of: * **`transactionType` is a human-readable label, not a stable enum** — values look like `"Account Transfer - In"` or `"Add Money"`, not `TRANSFER` or `DEPOSIT`. The set is open and the wording may change. Don't switch on it in code; use `recordId` and the amount/sign for logic, and treat this field as display text. * **Only `PENDING` and `SETTLED` transactions are returned** — declined and other non-baseline records are excluded, matching the single-user transactions surface. * **Ordering is newest first** across the whole flat stream (`createdAt` descending, ties broken by `recordId`, then by user). * **A transaction on a shared account appears once per grant that can see it** — two attributed rows with the same `recordId` and different `externalReferenceId`. `BulkTransaction` is a purpose-built, narrow type. It is **not** the full `Transaction` type returned by the single-user [transactions query](/features/get-all-transactions) — the fields above are the complete set. For richer per-transaction data, use the asynchronous export. ### `BulkTargetFailure` | Field | Type | Description | | --------------------- | --------- | ------------------------------------------------------ | | `externalReferenceId` | `String` | Your reference id for the target, when available. | | `accountId` | `UUID` | The target's account id, when the target was resolved. | | `code` | `String!` | Machine-readable failure reason (see below). | | `message` | `String!` | Human-readable failure detail. | | `code` | Meaning | | --------------------------- | ------------------------------------------------------------------------ | | `TARGET_NOT_CONNECTED` | The id is not a user currently connected to your application. | | `INVALID_TARGET_IDENTIFIER` | The id is not a valid identifier. | | `INSUFFICIENT_SCOPE` | The user's grant is missing `LIST_PAYMENT` and/or `LIST_PURCHASES`. | | `ACCOUNT_NOT_PERMITTED` | The grant does not permit this operation on the requested spend account. | A failed target never fails the request. **Select both identifiers.** `externalReferenceId` is `null` for any grant you connected without one, so on its own it may not tell you which user failed. `accountId` is populated whenever the target resolved — which is the case for `INSUFFICIENT_SCOPE` and `ACCOUNT_NOT_PERMITTED` — and is `null` only for `TARGET_NOT_CONNECTED` and `INVALID_TARGET_IDENTIFIER`, where nothing resolved. Querying `errors { externalReferenceId accountId code message }` means every entry is identifiable by at least one of the two. ### Request-level errors Everything above is per-target. These reject the whole request, returning `data.getBulkTransactions: null` plus a GraphQL error. **Branch on `extensions.code`, never on the message text** — wording can change, codes won't. | Condition | `extensions.code` | `extensions.errorName` | `statusCode` | | ---------------------------------------------------------------------- | ------------------- | ------------------------- | ------------ | | Window wider than 365 days | `APPLICATIONS-0009` | `InvalidDateWindow` | 422 | | `createdGte` after `createdLte` | `APPLICATIONS-0009` | `InvalidDateWindow` | 422 | | Malformed or tampered `after` cursor | `APPLICATIONS-0010` | `InvalidPaginationCursor` | 422 | | No eligible targets — none connected, or none with the required scopes | `APPLICATIONS-0011` | `InvalidBulkRequest` | 422 | | Malformed request payload (bad `targetSpec`, empty `targets`) | `APPLICATIONS-0011` | `InvalidBulkRequest` | 422 | | More than 1,000 target users | `APPLICATIONS-0008` | `TargetLimitExceeded` | 422 | | Bulk API not enabled for your application | `APPLICATIONS-0003` | `BulkApiAccessDenied` | 403 | | Missing credentials, or a non-Basic scheme | `AUTH-0002` | `InvalidCredentials` | 401 | The two date failures share a code — both mean "the range you asked for isn't usable" — and the `message` distinguishes them: ```json theme={null} { "errors": [ { "message": "The requested window exceeds the 365-day maximum. Narrow the range, or use the asynchronous transactions export for a wider pull.", "path": ["getBulkTransactions"], "extensions": { "errorName": "InvalidDateWindow", "code": "APPLICATIONS-0009", "statusCode": 422, "userFriendly": true } } ], "data": { "getBulkTransactions": null } } ``` A cursor altered in any way — truncated, re-encoded, hand-edited — fails as `APPLICATIONS-0010` before it reaches the database. Treat it as "start again from page one", not as retryable. ## Paging Read the first page, then follow `nextCursor` until `hasMore` is `false`. Keep every other argument identical across pages — the cursor encodes a position in that specific query's ordering. ```graphql theme={null} # Page 1: omit `after`. Read errors here — they are first-page only. Ask for totalCount only # if you actually need it; it costs an extra scan. { getBulkTransactions(targetSpec: { mode: ALL_CONNECTED }, limit: 100, includeTotalCount: true) { transactions { recordId externalReferenceId amount createdAt } errors { externalReferenceId accountId code message } totalCount failedTargetCount nextCursor hasMore } } # Page 2+: pass the previous nextCursor. { getBulkTransactions( targetSpec: { mode: ALL_CONNECTED } limit: 100 after: "eyJjcmVhdGVkQXQiOiIyMDI2LTA2…" ) { transactions { recordId externalReferenceId amount createdAt } nextCursor hasMore } } ``` Because paging is keyset-based rather than offset-based, transactions written while you page won't shift rows across page boundaries or produce duplicates. ## Migrating from the per-target response | Before | Now | | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `results[]`, one entry per target user | `transactions[]`, one entry per transaction. Group client-side by `externalReferenceId` to rebuild the old shape. | | `results[].transactions` (full `Transaction`) | Top-level `transactions` of the narrower `BulkTransaction` type. | | `results[].error` | `errors[]` — a flat list of failed targets, first page only, capped at a 100-item sample. | | `results[].totalCount` (per target) | `totalCount` (request-wide, opt-in via `includeTotalCount`, first page only). Per-target totals are no longer returned. | | `results[].hasNextPage` (per target) | `hasMore` + `nextCursor` (request-wide keyset paging). | | `successCount` / `failureCount` | `succeededTargetCount` / `failedTargetCount`. | | 20 transactions per target, hard cap | No per-user cap. `limit` (default and max 100) bounds the page. | | 90-day window was a hard cap | 90 days is now the default; the hard cap is 365 days. | | 100 target users, hard cap | 1,000 target users. | ## When to use the export instead `getBulkTransactions` can walk complete history by paging, so the export is for pulls you'd rather not page through — more than 1,000 users, windows longer than a year, scheduled batch jobs, or when you need richer per-transaction fields than `BulkTransaction` carries. Submit `GET_TRANSACTIONS_EXPORT` via `submitBulkOperation`, poll `getBulkOperationJob`, and download the NDJSON from the short-lived `resultUrl`. See [Bulk Operations](/bulk-api). # General API Error Codes Source: https://docs.fluz.app/get-started/general-api-error-codes These error codes are consistent across our API. | Error | Code | Error Message | | ------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------ | | INVALID\_ACCOUNT | AUTH-0001 | Your account does not have the right permission to login to this portal. Please Apply for an Enterprise Account. | | INVALID\_CREDENTIALS | AUTH-0002 | Incorrect auth details. | | PHONE\_NUMBER\_NOT\_FOUND | AUTH-0003 | Unable to find provided phone number or phone number it is not verified. Please select different method or contact customer support. | | INVALID\_PHONE\_NUMBER | AUTH-0004 | Invalid phone number. | | INVALID\_2FA\_TOKEN | AUTH-0005 | Access denied or expired. | | INCORRECT\_AUTH\_CODE | AUTH-0006 | Auth code provided is incorrect, please check and re-enter received code and try again. | | UNABLE\_TO\_SEND\_AUTH\_CODE | AUTH-0007 | Sorry, we were unable to send the auth code, please try again or contact customer support. | | INVALID\_USER\_ACCESS | AUTH-0008 | Invalid user access. | | INVALID\_PASSWORD | AUTH-0009 | The provided password is not valid. | | NO\_PIN\_EXISTS | AUTH-0010 | There is no passcode set on the user, please create one instead. | | PIN\_ALREADY\_SET | AUTH-0011 | There is already a passcode set on the user, please reset instead. | | INVALID\_PIN\_CODE | AUTH-0012 | The provided passcode does not match our records, please try again or reset your passcode. | | SOCIAL\_UNABLE\_TO\_LOGIN | AUTH-0013 | Unable to login with your social account. Please verify your credentials and try again or contact customer support. | | UNABLE\_TO\_CREATE\_PIN | AUTH-0014 | Unable to create new pass-code, please try again or contact customer support. | | UNABLE\_TO\_CHECK\_PIN | AUTH-0015 | Unable to check pass-code, please try again or contact customer support. | | UNABLE\_TO\_UPDATE\_PIN | AUTH-0016 | Unable to update pass-code, please try again or contact customer support. | | UNABLE\_TO\_RESET\_PIN | AUTH-0017 | Unable to reset pass-code, please try again or contact customer support. | | TRADITIONAL\_UNABLE\_TO\_LOGIN | AUTH-0018 | Unable to login with your account. Please verify your credentials and try again or contact customer support. | | INVALID\_JWT | AUTH-0019 | Invalid JWT Token! | | UNABLE\_TO\_RESEND\_2FA | AUTH-0020 | Unable to re-send 2FA! | | UNSUPPORTED\_PHONE\_NUMBER | AUTH-0021 | Provided number is not a valid mobile phone number! Please contact support for more details. | | REGISTRATION\_NOT\_ALLOWED | AUTH-0022 | Registration not allowed, please contact customer support! | | UNABLE\_TO\_CHANGE\_PASSWORD | AUTH-0023 | Unable to change password. Please try again or contact customer support. | | UNVERIFIED\_PHONE\_NUMBER | AUTH-0024 | Your phone number requires verification. Please contact support. | | UNSUCCESSFUL\_REGISTRATION | AUTH-0025 | Please try again again or contact support. | | REGISTRATION\_PHONE\_TAKEN | AUTH-0026 | The phone number you chose is already in use. | | REGISTRATION\_EMAIL\_TAKEN | AUTH-0027 | The email address you chose is already in use. | | UNABLE\_TO\_RESET\_PASSWORD | AUTH-0028 | Unable to reset password. | | SECURITY\_CODE\_EXPIRED | AUTH-0029 | Your 6-digit security code has expired. Please resend a new code. | | APPLICATION\_INACTIVE | AUTH-0030 | Your application is not active. | | INVALID\_SCOPE | AUTH-0031 | The requested scopes must be granted by the user first. | ## Address errors Address fields are validated across several flows. Constraints differ by context (for example, virtual card and bank account addresses are US-only, while business legal addresses may be international). For formatting rules and worked examples, see [Address Formatting Requirements](/concepts/address-formatting-requirements). | Error | Code | Applies to | Error Message | | --------------------------- | ------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | INVALID\_BILLING\_ADDRESS | VC-0025 | Virtual card issuance (billing address) | The billing address could not be verified. It must be a deliverable US address (no PO boxes) with a matching city, state, and ZIP. | | InvalidBusinessLegalAddress | BS-0002 | Business registration (KYB) | Business legal address is incorrect. | | InvalidOwnerInformation | BS-0003 | Business registration (KYB) | One or more owner details are incorrect (includes the owner's US-based address). | **Missing or malformed address fields** A missing required field or an invalid value inside an address object is returned as a general argument error — `ARG-0001` on virtual card calls, or `AR-0001` / `AR-0002` on business registration — rather than one of the address codes above. ## Next steps The error envelope shape, domain prefixes, and how to troubleshoot with request IDs. Formatting rules and worked examples for avoiding the address errors above. **Want to learn more?** Contact us at [partnerships@fluz.app](mailto:partnerships@fluz.app). Speak with our experts for more info or to request a demo. # Incomm Source: https://docs.fluz.app/in-comm This document provides specific details for using the Fluz Gift Card Vendor API Connector with Incomm as the vendor. # Original Incomm API vs Fluz API Adapter ### Base URL * Original Incomm API: [https://app.giftango.com](https://app.giftango.com) * Fluz API Adapter: * Staging: [https://api-adapter.staging.fluzapp.com/incomm](https://api-adapter.staging.fluzapp.com/incomm) * Production: [https://api-adapter.fluzapp.com/incomm](https://api-adapter.fluzapp.com/incomm) ### Original Incomm * Uses Bearer Authentication with the Authorization header - need to generate authorization token first. ```text theme={null} Authorization: Bearer ``` To generate AuthToken send request to `/auth/token` endpoint with body: ```text theme={null} { grant_type: 'client_credentials', client_id: INCOMM_CLIENT_ID, client_secret: INCOMM_SECRET_KEY, } ``` ### Fluz API Adapter * Uses Basic Authentication with the `Authorization` header (same as all vendor integrations). ```text theme={null} Authorization: Basic ``` ## API Endpoint Comparison | Operation | Incomm Endpoint | Fluz Endpoint | Notes | | :---------------- | :------------------------------------------------- | :---------------------------------------------------- | :--------------------------------------------------------------------- | | Get Order Details | `GET /orders/:orderId/cards` | `GET /v1/orders/:orderId/cards` | Retrieve card details for a specific order | | Get Order | `GET /orders/:orderId` | `GET /v1/orders/:orderId` | Retrieve details for a specific order | | Create Order | `POST /orders/immediate` | `POST /v1/orders/immediate` | Create a new gift card order | | Get Balance | `GET /programs/programs/:programId/programbalance` | `GET /v1/programs/programs/:programId/programbalance` | Get your account balance. Scoped to the account your key is issued for | # Request Examples ## Create Order ### Original Incomm Request: ```text theme={null} { "PurchaseOrderNumber": "cddabd6d-1f7a-483d-8126-8bc676f7bd63", "CustomerOrderId": "cddabd6d-1f7a-483d-8126-8bc676f7bd63", "Recipients": [ { "FirstName": "testFirstName", "LastName": "testLastName", "EmailAddress": "testEmail@test.com", "DeliverEmail": false, "Products": [ { "Sku": "VUSA-D-2500-00", "Quantity": 1, "Value": 25 } ] } ] } ``` ### Fluz API Adapter Request: ```text theme={null} { "PurchaseOrderNumber": "cddabd6d-1f7a-483d-8126-8bc676f7bd63", "CustomerOrderId": "cddabd6d-1f7a-483d-8126-8bc676f7bd63", "Recipients": [ { "FirstName": "testFirstName", "LastName": "testLastName", "EmailAddress": "testEmail@test.com", "DeliverEmail": false, "Products": [ { "Sku": "VUSA-D-2500-00", "Quantity": 1, "Value": 25 } ] } ] } ``` An order covers one recipient and one product. `DeliverEmail` must not be `true`, because Fluz returns the gift card credentials in the order response rather than emailing the recipient, which keeps delivery under your control. `PurchaseOrderNumber` and `CustomerOrderId` are kept for compatibility with your existing request shape; correlate an order using the identifiers Fluz returns. ## Response timing Create order is synchronous, always running to completion. The call does not return until the purchase has finished, which can take up to 150 seconds, so set your client's read timeout above that. **Order status values are Fluz's, not Incomm's.** `OrderStatus` returns `PENDING`, `IN_PROGRESS`, `COMPLETED`, `FAILED`, or `CANCELED`. See [Connector behaviour](/connector-behaviour) for status values, error responses, limits, and request constraints. # Example Usage with cURL ## Example 1: Get a Specific Order with Gift Cards ```text theme={null} # Get gift cards from order with Incomm via Fluz API Adapter curl -X GET "https://api-adapter.staging.fluzapp.com/incomm/v1/orders/353a4151-2d51-48b6-81c9-90fd6815f12b/cards" \ -H "Authorization: Basic " ``` ## Example 2: Get Order Details ```text theme={null} # Get order details with Incomm via Fluz API Adapter curl -X GET "https://api-adapter.staging.fluzapp.com/incomm/v1/orders/353a4151-2d51-48b6-81c9-90fd6815f12b" \ -H "Authorization: Basic " ``` ## Example 3: Get Balance ```text theme={null} # Get Balance with Incomm via Fluz API Adapter # The balance is scoped to your key's account, so any :programId value works curl -X GET "https://api-adapter.staging.fluzapp.com/incomm/v1/programs/programs/0/programbalance" \ -H "Authorization: Basic " ``` ## Example 4: Create a New Order ```text theme={null} # Create Order with Incomm via Fluz API Adapter # Note: this call blocks until the purchase completes. Allow up to 150 seconds. curl -X POST "https://api-adapter.staging.fluzapp.com/incomm/v1/orders/immediate" \ -H "Authorization: Basic " \ -H "Content-Type: application/json" \ -d '{ "PurchaseOrderNumber": "cddabd6d-1f7a-483d-8126-8bc676f7bd63", "CustomerOrderId": "cddabd6d-1f7a-483d-8126-8bc676f7bd63", "Recipients": [ { "FirstName": "testFirstName", "LastName": "testLastName", "EmailAddress": "testEmail@test.com", "DeliverEmail": false, "Products": [ { "Sku": "VUSA-D-2500-00", "Quantity": 1, "Value": 25 } ] } ] }' ``` # Managing External Reference IDs Source: https://docs.fluz.app/managing-external-reference-ids Use your own user identifiers to drive the Fluz API — how to assign one, where it appears across OAuth, widgets, transfers, and webhooks, and how to choose one you won't regret. An **external reference ID** is your own identifier for a Fluz user. It lets you operate the Fluz API using the IDs you already have in your system — without storing or passing Fluz's internal `userId` and `accountId` for every user. When a user authorizes your application, you attach your identifier to that grant. Fluz stores the pairing between your identifier and the user's Fluz account, scoped to your application. From that point forward you can reference the user by your own ID when generating tokens, sending transfers, and matching webhook events. The practical payoff: **you never have to build a Fluz-ID lookup table.** A webhook arrives, it carries your user ID, you route it. No join, no cache, no reconciliation job. ## One value, three field names The same value appears under a different name depending on the surface. This is the most common source of confusion on this page, so it's worth memorizing before you start. | Surface | Field name | Direction | | :------------------------------------ | :------------------------------ | :--------- | | OAuth authorization URL | `external_id` (query parameter) | You → Fluz | | Widget pre-approved transaction token | `externalId` (JWT claim) | You → Fluz | | GraphQL API | `externalReferenceId` | Both | | Webhook event payloads | `externalReferenceId` | Fluz → You | | OAuth access tokens | embedded in the issued token | Fluz → You | Because it's embedded in the access token, the association survives token refreshes — you assign it once, at grant time, and it persists. *** ## Choosing an identifier **Never use PII.** No email addresses, phone numbers, or names. Two reasons, both concrete. First, they change — people switch emails and phone numbers, and your mapping breaks permanently because the value is immutable once set. Second, this value travels in **query strings** and **JWT claims**, which means it lands in browser history, referrer headers, proxy logs, and your own application logs. Don't put personal data there. | Use | Avoid | Why | | :----------------------------------------- | :----------------------------------------- | :-------------------------------------------- | | Your database primary key | Email address, phone number | Mutable, and it's PII in a URL | | A UUID you mint per user | Sequential integers | Enumerable; also leaks your user count | | An opaque, prefixed ID like `usr_8f3d2a91` | An order ID, session ID, or transaction ID | Maps to one event, not one person — see below | **The most expensive mistake is scoping it to the wrong thing.** An external reference ID identifies a *person*, permanently — not a transaction, a payout run, a session, or an order. If you pass a per-transaction identifier, the first transfer succeeds and the second one creates a second mapping to the same human, and you've forked one user into many with no way to merge them. If you find yourself generating a new value for each operation, you want an idempotency key, not an external reference ID. ### The rules | Property | Rule | | :---------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Type | Any string. Fluz does not impose a format. | | Uniqueness | Must be unique per user **within your application**. One external reference ID maps to exactly one Fluz user. | | Scope | Scoped to your OAuth client. The same Fluz user can carry a different external reference ID in another developer's application, and no other application can see or use yours. | | Stability | Immutable in practice. Once set on a grant, it is never overwritten. | | Environment | Mappings live with the application, so staging and production mappings are entirely separate. Nothing you create in staging exists in production. | *** ## Assigning one ### Standard OAuth applications Append `external_id` to the authorization URL when you send the user to consent (see [Client-facing OAuth grant flow](/client-facing-o-auth-grant-flow)): ```text theme={null} https://fluz.app/authorize?response_type=code&client_id=&redirect_uri=&scopes=MAKE_DEPOSIT%20LIST_PAYMENT&state=&external_id=usr_8f3d2a91 ``` When the user completes the grant, Fluz records `usr_8f3d2a91` against that user's authorization of your application. Optional here, but strongly recommended. Adopting it later means backfilling grants one re-authorization at a time. ### Widget applications **Required.** All widget application types — deposit, payout, pay-in, virtual card, gift card catalog, bill pay, and external payout — require an external reference ID to establish a user session. Without one the request is rejected: ```text theme={null} External reference ID is required for applications ``` For widgets, the value travels as the **`externalId` claim inside the signed pre-approved transaction token**, alongside the amount and transaction type. You generate it server-side; it is not a client-side init option. See [Set Up Your Server](/developers/setting-up-your-server). ```javascript theme={null} import jwt from 'jsonwebtoken'; import { v4 as uuidv4 } from 'uuid'; const generatedToken = jwt.sign( { amount, apiKey: process.env.FLUZ_API_KEY, transactionType, // DEPOSIT (Pay-In) or WITHDRAW (Payout) externalId: 'usr_8f3d2a91', // your user, stable across every session jti: uuidv4(), // unique per transaction — not the same thing }, process.env.FLUZ_API_SECRET, { expiresIn: '1 day' } ); ``` `externalId` and `jti` sit next to each other in the same token and answer different questions. `externalId` is *who* — stable for the life of the user. `jti` is *which transaction* — new every time. Reusing `jti` breaks idempotency; changing `externalId` forks your user. *** ## Using one ### Address transfer destinations When creating a wallet transfer to another Fluz account (see [Transfer to Another Fluz Wallet](/features/transfer-to-another-fluz-wallet)), identify the destination by your own ID instead of a Fluz account ID: ```graphql theme={null} mutation { createTransfer(input: { idempotencyKey: "1f7c5a2e-9b14-4c6d-8e3f-2a90d4b7c1aa" amount: 25.00 destination: { externalReferenceId: "usr_8f3d2a91" } }) { transferId } } ``` Provide either `destination.accountId` or `destination.externalReferenceId` — never both. The destination user must have authorized your application, or the transfer is rejected. ### Match webhook events to your users Webhook payloads carry `externalReferenceId`, so you can route events without a lookup table: ```json theme={null} { "userId": "5070d5a1-d71a-4190-91b0-f116eec51771", "accountId": "9c2e1b44-7a3d-4f08-b6e5-d18a3c7f0e22", "externalReferenceId": "usr_8f3d2a91", "eventType": "DEPOSIT_COMPLETE", "amount": 100.00 } ``` Handle the field being absent. `externalReferenceId` is omitted where the user's grant has no external reference ID associated, or where the event is flagged as private. A handler that assumes the field is always present will throw on those deliveries — and a webhook handler that throws is a webhook you didn't process. See [Configure App Widget](/developers/configure-app-widget) for webhook setup. ### Getting user-scoped tokens `generateUserAccessToken` does **not** accept an `externalReferenceId` — it identifies the user by `userId` and `accountId` (see [Generate a User Access Token](/recipes/generate-user-access-token)). For users you reference by your own ID, use the OAuth flow instead. The grant already carries your identifier, and the tokens you get by exchanging the authorization code at `/token/exchange` are issued for that user with the association embedded. See [Exchange an OAuth Authorization Code](/exchange-an-o-auth-authorization-code). *** ## End to end One user, one identifier, four surfaces. User `usr_8f3d2a91` in your database clicks **Connect Fluz**. You redirect to `/authorize` with `external_id=usr_8f3d2a91`. They sign in, verify if needed, and approve your scopes. Fluz binds `usr_8f3d2a91` to their account, for your application only. Your callback exchanges the `code` for an `accessToken` and `refreshToken`. The association is embedded, so it survives every future refresh. You store the tokens against `usr_8f3d2a91` — no Fluz UUIDs in your schema. You pay them out with `destination: { externalReferenceId: "usr_8f3d2a91" }`, using your own ID as the address. The completion webhook arrives carrying `externalReferenceId: "usr_8f3d2a91"`. You route it straight to that user's record and mark the payout settled. No join, no lookup, no cache. *** ## Lifecycle ### Backfilling an existing grant If a user authorized your application before you adopted external reference IDs, supply one on a subsequent authorization and Fluz backfills it onto the existing grant — provided the grant doesn't already carry one. An existing value is never overwritten. ### Re-authorizing with a different value Because an existing value is never overwritten, passing a *different* `external_id` for a user who already has one does not change the mapping. Plan on the first value being permanent. If your user IDs are unstable, mint a dedicated immutable ID for Fluz rather than reusing something you might migrate. ### Deleting users on your side Never recycle an identifier. If you hard-delete a user and later reissue the same primary key to a different person, that new person inherits the old mapping — and the old person's Fluz account. Use UUIDs, or a monotonic sequence you never reset. *** ## Validation rules and errors | Scenario | Result | | :------------------------------------------------------------------- | :----------------------------------------------------------------------------------- | | `externalReferenceId` not found for your application | `No user found with externalReferenceId .` | | Transfer destination with both `accountId` and `externalReferenceId` | `Provide either destination.accountId or destination.externalReferenceId, not both.` | | Transfer destination user has not authorized your application | `Destination account has not authorized this application.` | | Widget session created without an external reference ID | `External reference ID is required for applications` | ## Troubleshooting | Symptom | Almost always | | :------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------- | | `No user found with externalReferenceId` for a user you know exists | Wrong environment — the mapping was created in staging and you're calling production, or the reverse | | Same person appears as multiple Fluz users | A per-transaction or per-session value was passed instead of a per-user one | | Value you passed on re-authorization didn't take effect | The grant already carried one; existing values are never overwritten | | Webhook handler crashes intermittently | Field absent on some deliveries — grants without an association, or events flagged private | | Widget session rejected | Widget types require the `externalId` claim in the `patToken`; confirm it's in the signed payload, not just in your init options | | Transfer rejected as unauthorized | Destination user hasn't authorized your application, regardless of the mapping existing | *** ## What an external reference ID is not * **Not** a Fluz `userId` or `accountId`. Those are Fluz-issued UUIDs; this one is issued by you. * **Not** an idempotency key. That's `idempotencyKey` on API calls and `jti` in widget tokens, and it's unique per operation. This is unique per person. * **Not** the `state` parameter in the OAuth flow. `state` is per-authorization-attempt CSRF protection and is not stored. * **Not** the external account identifiers that appear on withdrawal records or linked funding sources. Those reference banking and processor records, not users. *** ## Next steps Where you assign the identifier for OAuth apps. Where you assign it for widget apps. Addressing transfers by your own ID. Receiving events that carry it back. # Create a Virtual Card Source: https://docs.fluz.app/recipes/create-virtual-card Issue a virtual card with a spend limit and funding source. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const CREATE_VIRTUAL_CARD = gql` mutation { createVirtualCard( input: { idempotencyKey: "07df5653-43a8-4532-9881-3ab5857bbe12" spendLimit: 123.45 spendLimitDuration: DAILY lockDate: "2030-10-10" lockCardNextUse: true cardNickname: "Team travel" primaryFundingSource: FLUZ_BALANCE offerId: "b3355504-ad30-4b2f-873d-b8795277b918" } ) { virtualCardId cardholderName expiryMonth expiryYear virtualCardLast4 status initialAmount createdAt } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(CREATE_VIRTUAL_CARD); console.log(response); ``` Requires the `CREATE_VIRTUALCARD` scope. Related: [Virtual cards](/features/virtual-cards). # Create a Virtual Card Bulk Order Source: https://docs.fluz.app/recipes/create-virtual-card-bulk-order Issue many virtual cards in a single asynchronous request. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const CREATE_VIRTUAL_CARD_BULK_ORDER = gql` mutation CreateVirtualCardBulkOrder { createVirtualCardBulkOrder( input: { offerId: "592c394e-26cc-44ac-a145-a5f81301fe77" orderItems: [ { quantity: 3 spendLimit: 100 spendLimitDuration: DAILY lockCardNextUse: true cardNickname: "Team card" primaryFundingSource: FLUZ_BALANCE } ] } ) { orderId orderStatus } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(CREATE_VIRTUAL_CARD_BULK_ORDER); console.log(response); ``` Poll `getVirtualCardBulkOrderStatus` with the returned `orderId` to fetch the created cards. # Money Movement Source: https://docs.fluz.app/recipes/deposit-funds Check balances and deposit funds into your Fluz cash balance. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; import crypto from 'crypto'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const MIN_DEPOSIT_AMOUNT = 100; async function deposit(userToken) { const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer ${userToken}`, 'Content-Type': 'application/json', }, }); const DEPOSIT = gql` mutation depositCashBalance($input: DepositCashBalanceInput!) { depositCashBalance(input: $input) { cashBalanceDeposits { cashBalanceDepositId status } } } `; const input = { idempotencyKey: crypto.randomUUID(), amount: 100, depositType: 'CASH_BALANCE', bankAccountId: '<>', }; const res = await client.request(DEPOSIT, { input }); return res.depositCashBalance; } ``` Requires the `MAKE_DEPOSIT` scope. Related: [Pay-ins](/features/deposit-from-external-accounts). # Edit a Virtual Card Source: https://docs.fluz.app/recipes/edit-virtual-card Update the spend limit, nickname, or lock date of an existing card. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const EDIT_VIRTUAL_CARD = gql` mutation { editVirtualCard( input: { virtualCardId: "59c7e092-7d5b-4ae7-946c-f119c93a830c" spendLimit: 200.00 spendLimitDuration: LIFETIME lockDate: "2032-10-10" lockCardNextUse: false cardNickname: "Updated nickname" primaryFundingSource: FLUZ_BALANCE } ) { virtualCardId cardholderName virtualCardLast4 status initialAmount } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(EDIT_VIRTUAL_CARD); console.log(response); ``` Requires the `EDIT_VIRTUALCARD` scope. # Generate a User Access Token Source: https://docs.fluz.app/recipes/generate-user-access-token Exchange API credentials for a scoped user access token. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const API_KEY = ''; const USER_ID = ''; const ACCOUNT_ID = ''; const SCOPES = [ 'LIST_PURCHASES', 'LIST_OFFERS', 'LIST_PAYMENT', 'REVEAL_GIFTCARD', 'PURCHASE_GIFTCARD', 'MANAGE_PAYMENT', ]; const GENERATE_USER_ACCESS_TOKEN = gql` mutation generateUserAccessToken( $userId: UUID! $accountId: UUID! $scopes: [ScopeType!]! $seatId: UUID ) { generateUserAccessToken( userId: $userId accountId: $accountId scopes: $scopes seatId: $seatId ) { token scopes } } `; async function generateUserAccessToken() { const client = new GraphQLClient(API_URL, { headers: { Authorization: `Basic ${API_KEY}`, 'Content-Type': 'application/json', }, }); const data = await client.request(GENERATE_USER_ACCESS_TOKEN, { userId: USER_ID, accountId: ACCOUNT_ID, scopes: SCOPES, }); console.log('Access Token:', JSON.stringify(data, null, 2)); } generateUserAccessToken(); ``` Related: [Authentication](/get-started/api-credentials). # Get Accounts Source: https://docs.fluz.app/recipes/get-accounts List all Fluz accounts associated with a user. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const API_KEY = 'your-api-key-here'; const USER_ID = 'user-id-uuid-here'; const GET_ACCOUNTS = gql` query getAccountsByUserId($userId: UUID!) { getAccountsByUserId(userId: $userId) { accountId type accountName } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Basic ${API_KEY}`, 'Content-Type': 'application/json', }, }); const data = await client.request(GET_ACCOUNTS, { userId: USER_ID }); console.log(JSON.stringify(data, null, 2)); ``` # Get Merchants Source: https://docs.fluz.app/recipes/get-merchants Fetch the merchant catalog with offers and stock information. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const GET_MERCHANTS = gql` query getMerchants($name: String, $paginate: OffsetInput) { getMerchants(name: $name, paginate: $paginate) { merchantId name slug offers { offerId type offerRates { maxUserRewardValue denominations } stockInfo { ... on StockInfoVariableType { __typename description maxDenomination minDenomination } ... on StockInfoFixedType { __typename denomination availableStock } } } } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer `, 'Content-Type': 'application/json', }, }); const res = await client.request(GET_MERCHANTS, { name: null, paginate: { limit: 20, offset: 0 }, }); console.log(JSON.stringify(res, null, 2)); ``` Paginate by increasing `offset` until you receive an empty array. Related: [Rewards / Gift cards](/gift-card-overview). # Get Virtual Card Balance Source: https://docs.fluz.app/recipes/get-virtual-card-balance Retrieve remaining balance and spend limit for one or more cards. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const GET_VIRTUAL_CARD_BALANCE = gql` query GetVirtualCardBalance { getVirtualCardBalance( input: { virtualCardIds: [ "bd3be748-a0fb-4193-80a7-88419bc72dab" "9e42fdab-3c23-4eae-bd69-1fa746886505" ] } ) { virtualCardId spentAmount remainingBalance spendLimit spendLimitDuration } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(GET_VIRTUAL_CARD_BALANCE); console.log(response); ``` Requires `PCI_COMPLIANCE` and `REVEAL_VIRTUALCARD` scopes. # Get Virtual Card Bulk Order Status Source: https://docs.fluz.app/recipes/get-virtual-card-bulk-order-status Check the status of a bulk order and retrieve the issued cards. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const GET_STATUS = gql` query GetVirtualCardBulkOrderStatus { getVirtualCardBulkOrderStatus( input: { orderId: "<>" } ) { orderStatus orderId virtualCards { cardNumber expiryMMYY cvv cardHolderName virtualCardId } successfulCardCreations failedCardCreations totalCards } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(GET_STATUS); console.log(response); ``` # Get Virtual Card Offers Source: https://docs.fluz.app/recipes/get-virtual-card-offers List active virtual card offers available on your account. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const GET_VIRTUAL_CARD_OFFERS = gql` query GetVirtualCardOffers { getVirtualCardOffers( input: { cardBrandLocked: false, cardType: DEBIT, cardNetwork: MASTERCARD } ) { offerId bin bankName rewardValue programLimits { dailyLimit weeklyLimit monthlyLimit } programName } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(GET_VIRTUAL_CARD_OFFERS); console.log(response); ``` Requires the `CREATE_VIRTUALCARD` scope. Related: [Virtual cards](/features/virtual-cards). # Get Virtual Card Transactions Source: https://docs.fluz.app/recipes/get-virtual-card-transactions Query virtual card activity by card, transaction type, or date range. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const GET_VIRTUAL_CARD_TRANSACTIONS = gql` query GetVirtualCardTransactions { getVirtualCardTransactions( input: { virtualCardIds: ["2ed71ba0-d457-47ed-8ceb-d3fe6ce5c900"] paginate: { limit: 20, offset: 0 } filters: { transactionTypes: [PURCHASE] } } ) { virtualCardId transactions { transactionDate transactionType transactionStatus merchantName transactionAmount } } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(GET_VIRTUAL_CARD_TRANSACTIONS); console.log(response); ``` Requires `PCI_COMPLIANCE` and `REVEAL_VIRTUALCARD` scopes. # Lock a Virtual Card Source: https://docs.fluz.app/recipes/lock-virtual-card Temporarily block a virtual card from further transactions. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const LOCK_VIRTUAL_CARD = gql` mutation LockVirtualCard { lockVirtualCard( input: { virtualCardId: "cc7eaf27-1560-425c-92c4-bdc88d45d261" } ) { virtualCardId locked } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(LOCK_VIRTUAL_CARD); console.log(response); ``` Requires the `EDIT_VIRTUALCARD` scope. # Purchase a Gift Card Source: https://docs.fluz.app/recipes/purchase-gift-card End-to-end example: generate a token, purchase a gift card, and reveal its details. Complete flow that generates a user access token, purchases a gift card by merchant slug, and reveals the redemption details. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; import crypto from 'crypto'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const API_KEY = 'your-api-key-here'; const USER_ID = 'user-id-uuid-here'; const ACCOUNT_ID = 'account-id-uuid-here'; const generateUserToken = async (userId, accountId) => { const client = new GraphQLClient(API_URL, { headers: { Authorization: `Basic ${API_KEY}`, 'Content-Type': 'application/json', }, }); const GENERATE_USER_ACCESS_TOKEN = gql` mutation generateUserAccessToken( $userId: UUID! $accountId: UUID! $scopes: [ScopeType!]! $seatId: UUID ) { generateUserAccessToken( userId: $userId accountId: $accountId scopes: $scopes seatId: $seatId ) { token scopes } } `; const variables = { userId, accountId, scopes: [ 'LIST_PURCHASES', 'REVEAL_GIFTCARD', 'LIST_OFFERS', 'PURCHASE_GIFTCARD', 'LIST_PAYMENT', 'MANAGE_PAYMENT', 'MAKE_DEPOSIT', ], seatId: null, }; const res = await client.request(GENERATE_USER_ACCESS_TOKEN, variables); return res.generateUserAccessToken.token; }; const purchaseGiftCard = async (userToken, slug, amount) => { const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer ${userToken}`, 'Content-Type': 'application/json', }, }); const PURCHASE_GIFT_CARD = gql` mutation purchaseGiftCard($input: PurchaseGiftCardInput!) { purchaseGiftCard(input: $input) { purchaseId purchaseDisplayId purchaseAmount giftCard { giftCardId status createdAt } } } `; const variables = { input: { idempotencyKey: crypto.randomUUID(), amount, merchantSlug: slug, minRewardRate: 2.0, }, }; return client.request(PURCHASE_GIFT_CARD, variables); }; const revealGiftCard = async (userToken, giftCardId) => { const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer ${userToken}`, 'Content-Type': 'application/json', }, }); const REVEAL_GIFT_CARD = gql` mutation revealGiftCardByGiftCardId($giftCardId: UUID!) { revealGiftCardByGiftCardId(giftCardId: $giftCardId) { code pin url } } `; return client.request(REVEAL_GIFT_CARD, { giftCardId }); }; (async () => { const userToken = await generateUserToken(USER_ID, ACCOUNT_ID); const giftCard = await purchaseGiftCard(userToken, 'aero', 10); const giftCardId = giftCard.purchaseGiftCard.giftCard.giftCardId; const details = await revealGiftCard(userToken, giftCardId); console.log('Gift Card Details:', JSON.stringify(details, null, 2)); })(); ``` Related: [Purchase Gift Cards](/gift-card-overview), [Authentication](/get-started/api-credentials). # Request a Document Verification Link Source: https://docs.fluz.app/recipes/request-document-verification-link Generate a KYC document verification link to send to a user. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql'; const REQUEST_DOC_VERIFICATION = gql` mutation RequestDocumentVerificationLink($input: RequestDocumentVerificationLinkInput!) { requestDocumentVerificationLink(input: $input) { userId verificationType verificationId verificationUrl status message } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(REQUEST_DOC_VERIFICATION, { input: { gaveConsent: true, prefillData: true, }, }); console.log(response); ``` Related: [KYC & KYB](/user-kyc-verification). # Reveal a Virtual Card Source: https://docs.fluz.app/recipes/reveal-virtual-card Retrieve the PAN, CVV, and expiry for an issued virtual card. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const REVEAL_VIRTUAL_CARD = gql` mutation RevealVirtualCardByVirtualCardId { revealVirtualCardByVirtualCardId( virtualCardId: "c107e50b-10f3-449c-92c0-609d9a8cfa2a" ) { cardNumber expiryMMYY cvv cardHolderName billingAddress { streetAddress postalCode city state } } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(REVEAL_VIRTUAL_CARD); console.log(response); ``` Requires the `REVEAL_VIRTUALCARD` scope. # Set a Virtual Card PIN Source: https://docs.fluz.app/recipes/set-virtual-card-pin Enqueue PIN updates on all eligible virtual cards. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.staging.fluzapp.com/api/v1/graphql'; const SET_VIRTUAL_CARD_PIN = gql` mutation SetVirtualCardPIN { setVirtualCardPIN(input: { pin: "1234" }) { success pinError } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(SET_VIRTUAL_CARD_PIN); console.log(response); ``` Requires the `CREATE_VIRTUALCARD` scope. # Request KYC Autofill Source: https://docs.fluz.app/recipes/verify-user-autofill Request an identity decision using data already on file for the customer — no fields to submit. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql'; const VERIFY_USER_PREFILL_INFORMATION = gql` mutation verifyUserPrefillInformation { verifyUserPrefillInformation { status message } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(VERIFY_USER_PREFILL_INFORMATION); console.log(response); ``` Related: [KYC & KYB](/user-kyc-verification). # Request a User KYC Verification Source: https://docs.fluz.app/recipes/verify-user-kyc Submit user identity information for immediate KYC verification. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql'; const VERIFY_USER_INFORMATION = gql` mutation verifyUserInformation { verifyUserInformation( input: { firstName: "John" lastName: "Smith" streetLine1: "123 Main St" streetLine2: "" city: "Los Angeles" state: "CA" postalCode: "91234" country: "United States" dateOfBirth: "01/28/1975" ssnLast4: "1234" } ) { status message } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(VERIFY_USER_INFORMATION); console.log(response); ``` Related: [KYC & KYB](/user-kyc-verification). # Refresh OAuth Access Token Source: https://docs.fluz.app/refresh-o-auth-access-token You can refresh an accessToken received from the exchange of the authorization `code` To refresh an access token, make a request to /token/refresh with the following query params: | query param | description | | -------------- | ---------------------------------------------------- | | refresh\_token | The refreshToken token from the auth token exchange. | Additionally, set an Authorization header that is a base64 encoded string that is a combination of your client\_id:app\_secret. This is a Basic auth header, and so should follow the following format: `Authorization: Basic ` For example, if your `client_id` is `abc123` and your `client_secret` from the OAuth configuration is `def456`, the base64 encoded value would be `YWJjMTIzOmRlZjQ1Ng==`. The `client_id` and `client_secret` can be found in the `Overview` tab of the [Configure OAuth App](/configure-o-auth-app). Here is an example cURL command: ```text theme={null} curl -X GET "https://uni.staging.fluzapp.com/token/refresh?refresh_token=" -H "Authorization: Basic YWJjMTIzOmRlZjQ1Ng==" ``` The response will include: | query param | description | | :----------- | :--------------------------------------------------------------------- | | accessToken | Short-lived token for subsequent requests into Fluz‘s backend services | | refreshToken | refresh token to store and use when the accessToken becomes expired | | scopes | the values the user permitted through the previous flow | Here is an example of a full response: ```json theme={null} {"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTc1MjA5NDgwNH0.0dKCtVpN0mTHHMuGNNx4VLJisTnovFNPQKhSw6zWosc","authorizationCode":"f082972eb110b80f73b0d0f95d1de9266069a1e4","accessTokenExpiresAt":"2025-07-08T21:05:30.673Z","refreshToken":"8ec16c25951616150b0332a4a6d66547","refreshTokenExpiresAt":"2025-08-08T20:55:30.738Z","scope":\["MAKE\_WIDTHDRAW","MAKE\_DEPOSIT","LIST\_PAYMENT"],"client":\{"id":"dab5c80e-0321-4c3a-988a-ffedfd64d8db","app\_id":"0a92d46e-edf4-422e-8c62-946051e5067b","app\_name":"First OAuth integration","grants":\["authorization\_code","client\_credentials","password","refresh\_token"],"redirectUris":\["http\://localhost:3035/oauth/finalize"],"accessTokenLifetime":600},"user":\{"id":"5070d5a1-d71a-4190-91b0-f116eec51771"}} ``` # Runa Source: https://docs.fluz.app/runa This document provides specific details for using the Fluz Gift Card Vendor API Connector with Runa as the vendor. ## Original Runa API vs Fluz API Adapter ### Base URL * **Original Runa API:** [https://playground.runa.io](https://playground.runa.io) * **Fluz API Adapter:** * Staging: [https://api-adapter.staging.fluzapp.com/runa](https://api-adapter.staging.fluzapp.com/runa) * Production: [https://api-adapter.fluzapp.com/runa](https://api-adapter.fluzapp.com/runa) ### Authentication **Runa Base URL** * Uses API Key Authentication with the header `X-Api-Key` ```text theme={null} X-Api-Key: ``` ### Fluz API Adapter * Uses Basic Authentication with the `Authorization` header (same as all vendor integrations). ```text theme={null} Authorization: Basic ``` ## API Endpoint Comparison | Operation | Runa Endpoint | Fluz Endpoint | Notes | | :-------------------------- | :----------------------------- | :----------------------------- | :----------------------------------------------------------------------------------- | | Get Order | `GET /v2/order/:id` | `GET /v2/order/:id` | Retrieve details for a specific order | | Get All Orders | `GET /v2/order` | `GET /v2/order` | Retrieve the 100 most recent orders | | Create Order | `POST /v2/order` | `POST /v2/order` | Create a new gift card order | | Get Balance | `GET /v2/balance` | `GET /v2/balance` | Get your account balance. Returns an array of one | | Get Balance (single object) | `GET /v2/balance?currency=USD` | `GET /v2/balance?currency=USD` | The same balance, returned as a single object rather than an array. Balances are USD | # Request Examples ## Create Order ### **Original Runa Request:** ```text theme={null} { "payment_method": { "type": "ACCOUNT_BALANCE", "currency": "USD" }, "items": [ { "distribution_method": { "type": "PAYOUT_LINK" }, "products": { "type": "SINGLE", "value": "1800FL-US" }, "face_value": 10 } ], "description": "string" } ``` ### Fluz API Adapter Request: ```text theme={null} { "payment_method": { "type": "ACCOUNT_BALANCE", "currency": "USD" }, "items": [ { "distribution_method": { "type": "PAYOUT_LINK" }, "products": { "type": "SINGLE", "value": "1800FL-US" }, "face_value": 10 } ], "description": "d8e118ab-732b-4884-8e8a-70746b5f359e" } ``` The request shape is unchanged from Runa's. Only the brand code in `products.value` has to change, because it is resolved against the Fluz catalog. Correlate an order using the `id` from the response. ## Background Processing for Runa Orders All purchase operations through the Runa integration are processed as background operations. The Fluz API Adapter provides two response modes for Runa: ### Synchronous vs Asynchronous Processing Modes * **Synchronous Mode:** * Add header X-Execution-Mode: sync to wait for the background operation to complete * The API call will wait until the background purchase operation completes * Full operation results are returned in the response * Best for testing and low-volume flows, since the call stays open for the length of the purchase * **Asynchronous Mode (Default):** * Returns immediately with an operation reference ID * The purchase continues processing in the background * Check the status later by querying the order endpoint with the reference ID * Recommended for production, since your call returns immediately regardless of how long the purchase takes Both modes rely on background processing, but they differ in how the API responds to the client. **An order is readable once its purchase has completed.** Reading a reference ID before then returns an error rather than a pending status, so treat an error on a freshly created order as still processing and read again shortly. Order status values are Fluz's: `PENDING`, `IN_PROGRESS`, `COMPLETED`, `FAILED`, `CANCELED`. See [Connector behaviour](/connector-behaviour) for status values, error responses, limits, and request constraints. ## Example Usage with cURL ### Example 1: Get a Specific Order ```text theme={null} # Get a specific order with Runa via Fluz API Adapter curl -X GET "https://api-adapter.staging.fluzapp.com/runa/v2/order/df263170-1c87-4e53-baf5-96258c3dd6b9" \ -H "Authorization: Basic " ``` ### Example 2: Get All Orders ```text theme={null} # Get all orders with Runa via Fluz API Adapter curl -X GET "https://api-adapter.staging.fluzapp.com/runa/v2/order" \ -H "Authorization: Basic " ``` ### Example 3: Get Balance ```text theme={null} # Get your account balance with Runa via Fluz API Adapter curl -X GET "https://api-adapter.staging.fluzapp.com/runa/v2/balance" \ -H "Authorization: Basic " ``` ### Example 4: Get Balance as a Single Object ```text theme={null} # Same balance as Example 3, returned as an object rather than an array curl -X GET "https://api-adapter.staging.fluzapp.com/runa/v2/balance?currency=USD" \ -H "Authorization: Basic " ``` ### Example 5: Create Order with Runa (Asynchronous Mode - Default) ```text theme={null} # Create Order with Runa via Fluz API Adapter (Asynchronous mode - default) # This will return quickly with a reference ID while processing continues in the background curl -X POST "https://api-adapter.staging.fluzapp.com/runa/v2/order" \ -H "Authorization: Basic " \ -H "Content-Type: application/json" \ -d '{ "payment_method": { "type": "ACCOUNT_BALANCE", "currency": "USD" }, "items": [ { "distribution_method": { "type": "PAYOUT_LINK" }, "products": { "type": "SINGLE", "value": "1800FL-US" }, "face_value": 10 } ], "description": "d8e118ab-732b-4884-8e8a-70746b5f359e" }' ``` ### Example 6: Create Order with Runa (Synchronous Mode) ```text theme={null} # Create Order with Runa via Fluz API Adapter (Synchronous mode) # This will wait for the background process to complete before responding curl -X POST "https://api-adapter.staging.fluzapp.com/runa/v2/order" \ -H "Authorization: Basic " \ -H "Content-Type: application/json" \ -H "X-Execution-Mode: sync" \ -d '{ "payment_method": { "type": "ACCOUNT_BALANCE", "currency": "USD" }, "items": [ { "distribution_method": { "type": "PAYOUT_LINK" }, "products": { "type": "SINGLE", "value": "1800FL-US" }, "face_value": 10 } ], "description": "d8e118ab-732b-4884-8e8a-70746b5f359e" }' ``` # Sola / Cardknox Source: https://docs.fluz.app/sola Email sales to request access to our BETA program of Payment Processing API connectors. # Specialized verticals Source: https://docs.fluz.app/specialized-verticals Gaming, prediction markets, sweepstakes, and money services businesses run on the same Fluz APIs as everyone else. Here's the review path, the forms, and what to expect. Fluz supports licensed operators across gaming, prediction markets, and money services. These programs use the **same APIs, the same tokens, and the same capabilities** as every other Platform client — nothing about the integration changes. What's different is the review that happens before you go live. Because these programs sit inside a regulated perimeter shared with our sponsor banks and card networks, we run an enhanced review to confirm your licensing, your controls, and the specific use case you're building. **You don't have to wait to build.** Staging access is available while your review is in progress. Most operators complete their integration in parallel and go live the week their use case is approved. ## Verticals we support Pick the review form that most closely matches your business. If you operate across more than one — a sportsbook with a casino product, for example — start with your primary vertical and tell us about the rest in the form. Online and land-based casino operators. Start the review → State-licensed sportsbooks and betting operators. Start the review → Event contract and prediction market operators. Start the review → Skill gaming and competitive play platforms. Start the review → Daily and season-long fantasy sports operators. Start the review → Sweepstakes and promotional play operators. Start the review → Registered MSBs, including transmitters and exchangers. Start the review → ## How the review works Create your Fluz account and complete business verification. This establishes your legal entity, ownership, and control structure with us before anything else begins. [Start your KYB application →](http://fluz.app/kyb-application/) Choose the form above that best matches your business and fill it out. If more than one applies, submit the closest match and note the others in the form. This is the part that moves fastest when it's specific. Tell us exactly how you intend to use Fluz — which capabilities, for which flows, funding which users, in which states or markets. Vague descriptions cost weeks. Precise ones get approved. Our compliance team reviews the form against our standards, then evaluates how your use case maps to the Fluz platform and whether it opens the door to any additional solutions we can support. Expect follow-ups on licensing, funds flow, and controls. In most cases we'll also schedule a call with the Fluz risk team. This is a working conversation, not an interrogation — the operators who treat it that way move through it fastest. We compile the internal assessment and, where the program requires it, align with our sponsor bank, processor, or network partners. This step is largely out of your hands, and it's usually the longest one. You're approved for the use case as defined. Production credentials are released and you go live. Approvals are scoped to the use case you described. If you later want to expand into a new flow, market, or product, come back to us first — expansions are reviewed, but they're a conversation, not a restart. ## How long it takes The range is wide because the inputs vary. Three things drive where you land in it: the complexity of your business, the complexity of the solution you're building, and how transparently you work with our risk team. Operators who land near the fast end of the range tend to do the same few things: * **Have licensing documentation ready before you start.** State-by-state licenses, registrations, and any regulator correspondence. * **Describe one specific use case,** not a menu of everything you might eventually want to do. * **Answer follow-ups in days, not weeks.** Most of the elapsed time in a slow review is waiting on responses. * **Be upfront about the hard parts.** Prior terminations, chargeback history, ownership complexity, and pending regulatory matters all surface eventually. Surfacing them yourself is faster than us finding them, and it doesn't disqualify you. ## What we're evaluating Knowing what we look at makes the forms easier to fill out well: Which licenses you hold, which states or markets you operate in, and how you enforce those boundaries. Beneficial ownership, control persons, and corporate structure. Your compliance program, transaction monitoring, age and identity verification, and responsible play controls. Where money originates, where it settles, who holds it in between, and which Fluz capabilities sit in that path. ## Start the process If you're working with a Fluz sales rep, reach out to them directly. Otherwise, email [sales@fluz.app](mailto:sales@fluz.app) with a short description of your business and we'll point you to the right path. # Stripe Source: https://docs.fluz.app/stripe Email sales to request access to our BETA program of Payment Processing API connectors. # Submit a Bulk Operation Source: https://docs.fluz.app/submit-a-bulk-operation > Run a write or a large export across many connected users in one asynchronous call. You submit a job, poll its status, and (for exports and writes) download a results file. Runs a bulk operation across your connected users in one call. Unlike the synchronous reads ([Get Bulk Balances](/get-bulk-balances), [Get Bulk Transactions](/get-bulk-transactions)), `submitBulkOperation` is **asynchronous**: it validates the request, creates a job plus one item per target, and returns immediately with a `jobId`. The work runs in the background — [track the job](/track-a-bulk-job) to follow progress and retrieve results. Available in `staging` only at this point. One mutation covers all five operations, selected by the `operation` field: | `operation` | What it does | Required grant scopes (per user) | | ----------------------------- | --------------------------------------------------------------- | ---------------------------------------------------- | | `GET_BALANCES_EXPORT` | Export cash balances to a file | `LIST_PAYMENT` | | `GET_TRANSACTIONS_EXPORT` | Export transactions to a file | `LIST_PAYMENT`, `LIST_PURCHASES` | | `CREATE_TRANSFER` | Move funds between the operator and/or connected users | `MAKE_PAYOUT_TRANSFER_SEND` (on the **source** user) | | `DEPOSIT_CASH_BALANCE` | Fund a connected user's spend account from their payment method | `MAKE_DEPOSIT` | | `UPDATE_TRANSACTION_METADATA` | Edit memo / category on transactions | `LIST_PAYMENT`, `LIST_PURCHASES` | ## Requirements * `Authorization: Basic ` * The bulk API capability on your application. * The scopes the chosen operation needs, granted by each target user (see the table above). A target missing them becomes a per-item failure — it never fails the whole job. Every submit **must** carry an idempdempotencyKey` input or an`Idempotency-Key\` header (if both are sent they must match). Re-submitting the same key returns the original job instead of creating a dequest is byte-for-byte identical; a different request under the same key is rejected. There is no safe default for a fan-out write, so the key is required. ## Mutation ```graphql theme={null} mutation SubmitBulkOperation($input:) { submitBulkOperation(input: $input) { jobId status operation requestedTargetCount acceptedItemCount succeededItemCount failedItemCount skippedItemCount createdAt } } ``` A successful submit returns the job in `QUEUED` status. `acceptedItemCount` is how many targets will be processed; `skippedItemCountd up front (e.g. missing the required scope). Poll the job to watch `succeededItemCount`/`failedItemCount\` fill in — see [Track a Bulk Job](/track-a-bulk-job). ## Variables — export balances / transactions `exportOptions` bounds the window (defaults to the last 90 days; `GET_BALANCES_EXPORT` is a point-in-time snapshot and ignores tselection]\(/bulk-api#selecting-target-users) like the reads. ```json theme={null} { "input": { "operation": "GET_TRANSACTIONS_E "idempotencyKey": "export-2024-06-01-a", "targetSpec": { "mode": "ALL_CONNECTED" }, "exportOptions": { "createdGte": "2024-05-01T00:00:00Z", "createdLte": "2024-06-01T00:00:00Z", "includeMetadata": false } } } ``` ## Variables — create transfer Each item names its own `from` and `to` endpoint, so one job can mix directions: operator→user, user→operator, and user→user. An endpoint is **either** `{ "operator": true }` (your application's payout account) **or** `{ "externalReferenceId": "…" }` (a connected user). `from` and `to` must differ. The **source** user's grant RANSFER\_SEND`; the destination onlyneeds to be connected. A transfer stays within one sponsor bank. `targetSpec\` is not required for transfers — participants are taken f ```json theme={null} { "input": { "operation": "CREATE_TRANSFER", "idempotencyKey": "payouts-2024-06-01-a", "transferOptions": { "items": [ { "from": { "operator": true }, "to": { "externalReferenceId": "user-123" }, "amount": 10.00, "memo": "Reward" }, { "from": { "externalReferenceId": "user-123" }, "to": { "operator": true }, "amount": 2.50 }, { "from": { "externalReferen { "externalReferenceId": "user-456"}, "amount": 5.00 } ] } } } ``` ## Variables — deposit cash balance Funds a connected user's spend account from a payment method **they own** — exactly one of `bankCardId` or `bankAccountId` per own target user, so there's no`targetSpec` — the deposit targets are derived from the items (each user at most once). `userCashBalanceId` is optional (defted/default spend account). ```json theme={null} { "input": { "operation": "DEPOSIT_CASH_BALANCE", "idempotencyKey": "deposits-2024 "depositOptions": { "items": [ { "externalReferenceId": "user-123", "amount": 10.00, "bankCardId": "b1a2…", "memo": "Top-up" }, { "externalReferenceId": "us"bankAccountId": "c3d4…" } ] } } } ``` ## Variables — update transaction metadata Edits `memo` and/or `transactionCateions. Each item names its own targetuser, so there's no `targetSpec`— the targets are derived from the items (each user at most once). **At most 100 edits in total** per submission. A field set to`null\` clears it; an omitted field is left untouched. Metadata edits execute **synchronously** — the returned job is already terminal, so you can read per-edit results immediately without polling. ```json theme={null} { "input": { "operation": "UPDATE_TRANSACTION "idempotencyKey": "metadata-2024-06-01-a", "metadataOptions": { "items": [ { "externalReferenceId": "user-123", "edits": [ { "recordId": "3f2a…", "sactionCategory": "Meals" }, { "recordId": "9b7c…", "memo": null } ] } ] } } } ``` ## Response ```json theme={null} { "data": { "submitBulkOperation": { "jobId": "9c1e6f2a-1d4b-4a2e-8f0c-2b7e5a9d1234", "status": "QUEUED", "operation": "CREATE_TRANSFER" "requestedTargetCount": 3, "acceptedItemCount": 3, "succeededItemCount": 0, "failedItemCount": 0, "skippedItemCount": 0, "createdAt": "2024-06-01T15:04:05Z" } } } ``` ## Arguments | Parameter | Type | Description | | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `input.operation` | `BulkOperationType!` | One of `GET_BALANCES_EXPORT`, `GET_TRANSACTIONS_EXPORT`, `CREATE_TRANSFER`, `DEPOSIT_CASH_BALANCE`, | | `UPDATE_TRANSACTION_METADATA`. | | | | `input.idempotencyKey` | `String` | Your unique key for this submission. Required (via this field or the `Idempotency-Key` header). | | `input.targetSpec` | `BulkTargetSp users the operation applies to.Required **only** for the exports (`GET\_\*\_EXPORT`). The write operations — `CREATE\_TRANSFER`, `DEPOSIT\_CASH\_BALANCE`, `UPDATE\_TRANSACTION\_METADATA\` — name their targets in their own items, so | | | `targetSpec` is ignored for those. | | | | `input.exportOptions` | `BulkExportOptionsInput` | `createdGte` / `createdLte` window and `includeMetadata`. Export operations only. | | `input.transferOptions.items` | `[ Per-transfer `from`/`to`endpoints, `amount`, optional `memo`. `CREATE\_TRANSFER\` only. | | | `input.depositOptions.items` | `[Ber-target `amount`, one of`bankCardId`/`bankAccountId`, optional `userCashBalanceId`/`memo`. `DEPOSIT\_CASH\_BALANCE\` only. | | | `input.metadataOptions.items` | `[ Per-target `edits` (`recordId`,`memo?`, `transactionCategory?`). `UPDATE\_TRANSACTION\_METADATA\` only; ≤100 edits total. | | ## Response fields | Field | Description | | ---------------------------------------- | -------------------------------------------------------------------------------------- | | `jobId` | The job's id. Use it to (/track-bulk-job). | | `status` | Lifecycle status: `QUEUED`, `RUNNING`, `COMPLETED`, `COMPLETED_WITH_ERRORS`, `FAILED`, | | or `CANCELLED`. | | | `operation` | The submitted operation. | | `requestedTargetCount` | Targets addressed by the request. | | `acceptedItemCount` | Items that waccepted target). | | `succeededItemCount` / `failedItemCount` | Per-item outcomes; fill in as the job runs. | | `skippedItemCount` | Targets rejected at submit (e.g. missing the required scope). | | `createdAt` | When the job was created. | Failures are **per item** — one target's failure never fails the job (see the \[per-target failure contract]\(/bulk-api#per-target-failu itself is only rejected for an authfailure, an invalid operation, a cap exceeded, or when *every* target is unresolvable. After a terminal status, read per-item detai each write produced — with [Track aBulk Job](/track-a-bulk-job). ## Errors The whole submission is rejected (nohese cases. Everything else becomes a per-item result you read via [Track a Bulk Job](/track-a-bulk-job#per-item-failure-codes). | Error | HTTP | When | | ------------------------------------ | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BulkApiAccessDenied` | 403 | Your the bulk API capability. | | `IdempotencyKeyConflict` | 409 | The `Idempotency-Key` header and the `idempotencyKey` input were | | both sent and do not match. | | | | `IdempotencyKeyReused` | 409 | This idempotency key was already used for a **different** request. | | (An identical resubmit returns the o | | | | `TargetLimitExceeded` | 422 | The request references more than 10,000 distinct users. Split the batch and resubmit. | | `ArgumentsInvalid` | 400 | Payload transfer endpoint that isn't exactly one of operator/user, `from` == `to`, a sub-cent amount, an over-long memo, >100 metadata edits) **or** every target was unresolvable / missing the required scope. | | `AmbiguousSourceCashBalance` | 422 | *(`CREATE_TRANSFER` only)* An operator-sourced transfer, but your payout routing names multiple clt. Contact Fluz to configure adefault payout balance. | # Submit Business Documents Source: https://docs.fluz.app/submit-business-documents Upload supporting documentation for a business account — at registration for sole proprietorships, or when KYB review requests more information. ## Overview Business documents are submitted over a **REST file-upload endpoint**, not through the GraphQL API. The endpoint stores the file and returns a URL that you then reference from a GraphQL mutation or hand to your account manager. There are two moments in a business lifecycle where documents come into play: | Situation | What to do | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Registering a **sole proprietorship** | Upload the document **before** calling `registerBusiness`, then pass the returned URL in `soleProprietorshipDocumentUrl`. Registration will fail without it. | | KYB review requests **additional documentation** | The account stays in `PENDING`. Upload the requested files and provide the URLs to your account manager along with the `accountId`. | LLCs, corporations, partnerships, and co-ops do **not** require any document upload at registration. Fluz verifies those entities from the data submitted in [registerBusiness](/business-registration). Documents are only requested if the automated checks do not clear. *** ## Upload endpoint | Property | Value | | -------------- | -------------------------------------------------- | | Method | `POST` | | Path | `/api/v1/file-upload/sole-proprietorship-document` | | Content type | `multipart/form-data` | | Form field | `file` | | Authentication | OAuth Bearer Token | Staging base URL: `https://transactional-graph.staging.fluzapp.com` ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/file-upload/sole-proprietorship-document \ -H "Authorization: Bearer " \ -F "file=@/path/to/document.pdf" ``` The endpoint returns a URL. Store it and pass it straight through to `registerBusiness`: ```json theme={null} { "input": { "businessName": "Jane Doe Design", "businessStructure": "SOLE_PROPRIETORSHIP", "soleProprietorshipDocumentUrl": "", "...": "..." } } ``` Upload first, register second. There is no way to attach a document to an existing registration — if you submit a sole proprietorship without `soleProprietorshipDocumentUrl`, the mutation is rejected, and the user cannot start a second application until the first one closes (`BS-0007`). **File requirements** — accepted formats, maximum file size, and page limits: confirm current limits with your account manager before building client-side validation. *** ## Sole proprietorship documents Because a sole proprietorship is not a separately registered legal entity, the document needs to establish two things: that the trade name exists, and that the proprietor is the person behind it. Documents that generally satisfy this: * **Fictitious business name / DBA filing** — the county or state filing for the trade name * **Business license** — issued to the proprietor by the city, county, or state * **IRS EIN assignment letter** (CP 575 or 147C) — where the sole proprietorship holds its own EIN Practical guidance: * The name on the document should match the `businessName` you submit. A mismatch is the most common reason a sole proprietorship review stalls. * Submit a full, legible scan or PDF of the whole document, not a cropped screenshot of one line. * Expired licenses and filings are not accepted — the document should be current. ## Additional documentation during KYB review If Fluz compliance needs more than the registration payload provides, the KYB case stays `PENDING` while the request is made through your account manager. Commonly requested items: * Formation documents (articles of organization or incorporation, operating agreement, partnership agreement) * Proof of the business address * Government-issued ID for a beneficial owner or control person * An explanation or supporting detail on the nature of the business, where `natureOfBusiness` was left blank or was too general What to do on your side: 1. Keep the account in an "under review" state in your UI. Do not resubmit `registerBusiness` — a second application is blocked by `BS-0007`, and duplicate cases slow the review down. 2. Upload the requested files and send the URLs plus the `accountId` to your account manager. 3. Poll the business account status on a low-frequency schedule and update your UI when it moves to approved or declined. See [KYB status lifecycle](/business-registration#kyb-status-lifecycle). Submitting complete beneficial ownership information at registration is the single most effective way to avoid a document request. See [Beneficial ownership requirements](/business-registration#beneficial-ownership-requirements). ## Related pages The `registerBusiness` mutation, parameters, and error codes. Fetch the category and sub-category IDs required at registration. Identity verification for the individual owners behind the business. Base URLs for the upload endpoint in each environment. # Tabapay Source: https://docs.fluz.app/tabapay Email sales to request access to our BETA program of Payment Processing API connectors. # Tango Card Source: https://docs.fluz.app/tango This document provides specific details for using the Fluz Gift Card Vendor API Adapter with TangoCard as the vendor. ## Original TangoCard API vs Fluz API Adapter ### Base URL * Original TangoCard API: [https://integration-api.tangocard.com/rass](https://integration-api.tangocard.com/rass) * Fluz API Adapter: * Staging: [https://api-adapter.staging.fluzapp.com/tangocard](https://api-adapter.staging.fluzapp.com/tangocard) * Production: [https://api-adapter.fluzapp.com/tangocard](https://api-adapter.fluzapp.com/tangocard) ## Authentication ### Original TangoCard * Uses Basic Authentication with username and password. ```text theme={null} Username: Password: ``` ### Fluz API Adapter * Uses Basic Authentication with the `Authorization` header (same as all vendor integrations). ```text theme={null} Authorization: Basic ``` ## API Endpoint Comparison | Operation | TangoCard Endpoint | Fluz Endpoint | Notes | | :------------------------- | :--------------------------------- | :--------------------------------- | :--------------------------------------- | | Get Order | `GET /v2/orders/:referenceOrderID` | `GET /v2/orders/:referenceOrderID` | Retrieve details for a specific order | | Get All Orders | `GET /v2/orders/` | `GET /v2/orders` | Retrieve the 100 most recent orders | | Create Order | `POST /v2/orders/` | `POST /v2/orders/` | Create a new gift card order | | Get Balance (All Accounts) | `GET /v2/accounts` | `GET /v2/accounts` | Get balance information for all accounts | | Get Balance (By Account) | `GET /v2/accounts/:accountId` | `GET /v2/accounts/:id` | Get balance for a specific account | # Request Examples ## Create Order ### Original TangoCard Request: ```text theme={null} { "sendEmail": false, "accountIdentifier": "testing1234account", "customerIdentifier": "johnr002", "utid": "U561593", "amount": 50 } ``` ### Fluz API Adapter Request: ```text theme={null} { "sendEmail": false, "accountIdentifier": "testing1234account", "customerIdentifier": "johnr002", "utid": "1800FL-US", "amount": 40, "externalRefID": "d8e118ab-732b-4884-8e8a-70746b5f359e" } ``` Orders are funded from the account your Fluz API key is issued for, so `accountIdentifier` and `customerIdentifier` are kept for compatibility with your existing request shape. Correlate an order using `referenceOrderID` from the response. ## Response timing Create order is synchronous, always running to completion. The call does not return until the purchase has finished, which can take up to 150 seconds, so set your client's read timeout above that. **Order status values are Fluz's, not TangoCard's.** The `status` field returns `PENDING`, `IN_PROGRESS`, `COMPLETED`, `FAILED`, or `CANCELED`. A check against a TangoCard value such as `COMPLETE` will not match. See [Connector behaviour](/connector-behaviour) for status values, error responses, limits, and request constraints. ## Example Usage with cURL ### Example 1: Get a Specific Order ```text theme={null} # Get a specific order with TangoCard via Fluz API Adapter curl -X GET "https://api-adapter.staging.fluzapp.com/tangocard/v2/orders/72991f5a-b389-404e-98b0-ac5444839ca9" \ -H "Authorization: Basic " ``` ### Example 2: Get All Orders ```text theme={null} # Get all orders with TangoCard via Fluz API Adapter curl -X GET "https://api-adapter.staging.fluzapp.com/tangocard/v2/orders" \ -H "Authorization: Basic " ``` ### Example 3: Get Balance for All Accounts ```text theme={null} # Get Balance for all accounts with TangoCard via Fluz API Adapter curl -X GET "https://api-adapter.staging.fluzapp.com/tangocard/v2/accounts" \ -H "Authorization: Basic " ``` ### Example 4: Get Balance for a Specific Account ```text theme={null} # Get Balance for a specific account with TangoCard via Fluz API Adapter curl -X GET "https://api-adapter.staging.fluzapp.com/tangocard/v2/accounts/testing1234account" \ -H "Authorization: Basic " ``` ### Example 5: Create a New Order ```text theme={null} # Create Order with TangoCard via Fluz API Adapter # Note: this call blocks until the purchase completes. Allow up to 150 seconds. curl -X POST "https://api-adapter.staging.fluzapp.com/tangocard/v2/orders" \ -H "Authorization: Basic " \ -H "Content-Type: application/json" \ -d '{ "sendEmail": false, "accountIdentifier": "testing1234account", "customerIdentifier": "johnr002", "utid": "1800FL-US", "amount": 40, "externalRefID": "d8e118ab-732b-4884-8e8a-70746b5f359e" }' ``` # Track a Bulk Job Source: https://docs.fluz.app/track-a-bulk-job Poll a submitted bulk operation for progress, read per-target results, and download the result file. `submitBulkOperation` returns immediately with a `jobId`. Everything after that is polling: **`getBulkOperationJob`** for the job's counters, **`getBulkOperationItems`** for the per-target outcomes. Completion is polling-only — there are no bulk webhooks. ## Requirements * `Authorization: Basic ` (your application API key) * The **bulk API capability** on your application * The job must belong to your application — another app's job id returns not-found, identical to an unknown id ## Which one to call | You want | Use | | ---------------------------------------------- | ---------------------------------------------------------- | | Is it done? How many succeeded? | `getBulkOperationJob` — one row read, cheap enough to poll | | Which targets failed, and why | `getBulkOperationItems(status: FAILED)` | | The Fluz resource a write produced | `getBulkOperationItems` → `resultResourceId` | | The export file, or a whole-job results ledger | `getBulkOperationJob` → `resultUrl` | `getBulkOperationJob` reads only the job row — no per-item scan — so it's the one to poll on an interval. Reach for `getBulkOperationItems` once the job is terminal, or when you specifically need per-target detail. ## Poll the job ```graphql theme={null} query TrackBulkJob($bulkJobId: UUID!) { getBulkOperationJob(bulkJobId: $bulkJobId) { jobId status operation targetMode requestedTargetCount acceptedItemCount skippedItemCount succeededItemCount failedItemCount createdAt } } ``` ```json theme={null} { "data": { "getBulkOperationJob": { "jobId": "9f53f64d-898d-45cc-b7fb-890935cca664", "status": "COMPLETED_WITH_ERRORS", "operation": "CREATE_TRANSFER", "targetMode": "SELECTED", "requestedTargetCount": 250, "acceptedItemCount": 240, "skippedItemCount": 10, "succeededItemCount": 236, "failedItemCount": 4, "createdAt": "2026-08-05T14:12:27.556Z" } } } ``` ### Job fields | Field | Type | Meaning | | ---------------------- | -------------------- | ---------------------------------------------------------------------------------------- | | `jobId` | `UUID!` | Poll with this; also the key for results and cancellation. | | `status` | `BulkJobStatus!` | See below. | | `operation` | `BulkOperationType!` | Echoes what you submitted. | | `targetMode` | `BulkTargetMode!` | `SELECTED` or `ALL_CONNECTED`. | | `requestedTargetCount` | `Int!` | Resolved targets the job addresses — one item per target. | | `acceptedItemCount` | `Int!` | Items that will actually be processed. | | `skippedItemCount` | `Int!` | Resolved targets rejected **at submission**, before any work — typically missing scopes. | | `succeededItemCount` | `Int!` | Items finished successfully. | | `failedItemCount` | `Int!` | Items that ran and failed. | | `createdAt` | `DateTime!` | Submission time. | | `resultUrl` | `String` | Pre-signed download. See [Results file](#results-file). | | `resultUrlExpiresAt` | `DateTime` | When the current `resultUrl` stops working; `null` whenever `resultUrl` is. | **`skipped` and `failed` are not the same thing.** Skipped items never ran — they were rejected at submission and consumed no downstream call. Failed items ran and something went wrong. Reconciling a batch means reading both. The counters relate as `requestedTargetCount = acceptedItemCount + skippedItemCount`, and once terminal, `acceptedItemCount = succeededItemCount + failedItemCount`. ### Job status | Status | Meaning | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `QUEUED` | Accepted, not started. | | `RUNNING` | At least one item is in flight. | | `COMPLETED` | Every accepted item succeeded. | | `COMPLETED_WITH_ERRORS` | Finished, but at least one item failed. **This is a normal outcome, not an error** — one target's failure never fails the job. | | `FAILED` | The job as a whole could not be processed. | | `CANCELLED` | Cancelled via `cancelBulkOperation`. | Terminal statuses are `COMPLETED`, `COMPLETED_WITH_ERRORS`, `FAILED` and `CANCELLED`. Stop polling on any of them. `UPDATE_TRANSACTION_METADATA` executes **synchronously**. Its job is already terminal when `submitBulkOperation` returns, so there is nothing to poll — go straight to `getBulkOperationItems` for the per-edit results. ## Read per-target results ```graphql theme={null} query BulkJobItems($bulkJobId: UUID!, $status: BulkItemStatus, $limit: Int, $after: String) { getBulkOperationItems(bulkJobId: $bulkJobId, status: $status, limit: $limit, after: $after) { items { itemIndex externalReferenceId accountId status resultResourceId errorCode errorMessage } totalCount hasNextPage nextCursor } } ``` ```json theme={null} { "bulkJobId": "9f53f64d-898d-45cc-b7fb-890935cca664", "status": "FAILED", "limit": 100, "after": null } ``` ### Arguments | Argument | Type | Description | | ----------- | ---------------- | --------------------------------------------------------------------------- | | `bulkJobId` | `UUID!` | The job to read. | | `status` | `BulkItemStatus` | Filter to one status. Omit for all items. | | `limit` | `Int` | Items per page. Default and maximum are both `100`. | | `after` | `String` | Opaque cursor from a previous page's `nextCursor`. Omit for the first page. | ### Item fields | Field | Type | Meaning | | --------------------- | --------------------------- | ------------------------------------------------------------------------------------- | | `itemIndex` | `Int!` | Position within the job, matching submission order. | | `externalReferenceId` | `String` | Your reference id for the target, when the grant has one. | | `accountId` | `UUID` | The target's Fluz account id. | | `status` | `BulkItemStatus!` | `QUEUED`, `RUNNING`, `SUCCEEDED`, `FAILED`, `SKIPPED`, `CANCELLED`. | | `resultResourceId` | `String` | The Fluz resource this item produced — e.g. a transfer id. Present on success. | | `errorCode` | `String` | Machine-readable reason when `FAILED` or `SKIPPED`. | | `errorMessage` | `String` | Human-readable detail. | | `metadataResults` | `[BulkMetadataEditResult!]` | Per-edit results for `UPDATE_TRANSACTION_METADATA`. `null` for every other operation. | **`totalCount` is the job's unfiltered item total**, read from the job's own counters — *not* a count of the filtered page. Filtering to `FAILED` on a 240-item job still reports `totalCount: 240`. Page with `hasNextPage`/`nextCursor`; don't infer the end from `totalCount`. `itemIndex` is the stable join key back to your submission: item *n* corresponds to the *n*-th entry in the `items` array you submitted, so you can map a failure to the exact transfer or deposit you asked for even when `externalReferenceId` is absent. ### Reconciling a batch ```graphql theme={null} # Everything that ran and failed. { getBulkOperationItems(bulkJobId: "…", status: FAILED, limit: 100) { items { itemIndex externalReferenceId errorCode errorMessage } hasNextPage nextCursor } } # Everything rejected before it ran — usually a scope or permission problem on your side. { getBulkOperationItems(bulkJobId: "…", status: SKIPPED, limit: 100) { items { itemIndex externalReferenceId errorCode errorMessage } hasNextPage nextCursor } } # Map successes back to Fluz resources. { getBulkOperationItems(bulkJobId: "…", status: SUCCEEDED, limit: 100) { items { itemIndex externalReferenceId resultResourceId } hasNextPage nextCursor } } ``` ### Metadata edit results `UPDATE_TRANSACTION_METADATA` reports per **edit**, not just per target — one item per target user, each carrying a `metadataResults` entry for every transaction it tried to change: ```graphql theme={null} { getBulkOperationItems(bulkJobId: "…") { items { externalReferenceId status metadataResults { recordId success errorCode errorMessage } } } } ``` An item can be `SUCCEEDED` while individual edits inside it failed, so check `metadataResults[].success` rather than the item status alone. ## Results file For read exports (`GET_TRANSACTIONS_EXPORT`, `GET_BALANCES_EXPORT`) the output is an **NDJSON file**. For the write operations (`CREATE_TRANSFER`, `DEPOSIT_CASH_BALANCE`) it's a per-target results ledger — one row per target with its status, `resultResourceId` and any error. ```graphql theme={null} { getBulkOperationJob(bulkJobId: "…") { status resultUrl resultUrlExpiresAt } } ``` Four things to know: * **`resultUrl` is minted only when you select it.** Polling `status` without it stays cheap; the URL is signed on request. * **It's `null` until the job is terminal**, and stays `null` for jobs that produced no file — for example when every target was denied. * **It expires shortly after issue.** Check `resultUrlExpiresAt`; to get a fresh URL, just select `resultUrl` again. * Neither Fluz's API nor your integration is in the download path — the URL points straight at storage. ## Cancelling ```graphql theme={null} mutation { cancelBulkOperation(bulkJobId: "…") { status succeededItemCount failedItemCount skippedItemCount } } ``` Best-effort and irreversible. Items not yet started are cancelled; **items already running are allowed to finish and record their results**, so counters can still move after you cancel. Safe to call more than once — an already-finished or already-cancelled job comes back unchanged rather than erroring. ## Errors | Condition | `extensions.code` | `extensions.errorName` | `statusCode` | | --------------------------------------------------------- | ------------------- | ---------------------- | ------------ | | Unknown job id, or a job belonging to another application | `APPLICATIONS-0006` | `BulkJobNotFound` | 404 | | Bulk API not enabled for your application | `APPLICATIONS-0003` | `BulkApiAccessDenied` | 403 | | Missing credentials, or a non-Basic scheme | `AUTH-0002` | `InvalidCredentials` | 401 | An id that belongs to another application returns the **same** not-found response as an id that doesn't exist — job ids are not enumerable across applications. Branch on `extensions.code`, not on message text. ## Polling guidance Poll `getBulkOperationJob` — it reads one row and never scans items. A few seconds between polls is reasonable for small jobs; back off for large ones, since a 10,000-item job is paced by queue throughput, not by how often you ask. Stop on any terminal status. Then: 1. `COMPLETED` — nothing to reconcile. 2. `COMPLETED_WITH_ERRORS` — read `status: FAILED` items, and `SKIPPED` too if `skippedItemCount > 0`. 3. `FAILED` or `CANCELLED` — read the items to see how far it got before stopping. For exports, select `resultUrl` only once the status is terminal; before that it's always `null`. # TSYS Source: https://docs.fluz.app/tsys Email sales to request access to our BETA program of Payment Processing API connectors. # Overview Source: https://docs.fluz.app/user-kyc-verification Verify your customers' identities through Fluz — either by embedding the Fluz widget or by submitting verifications directly through the API. Fluz requires customers to be identity-verified (KYC) before they can perform certain transactions, and verification is what unlocks higher transaction limits. Until a customer is verified, parts of the platform — funding a wallet, withdrawing funds, and virtual card issuance — remain unavailable to them. There are two ways to get a customer verified. The choice comes down to whether you want Fluz to own the verification experience, or whether you want to own it yourself. Embed the Fluz widget. Verification is handled as a built-in step — Fluz collects everything from the customer, in Fluz-hosted UI. You collect and store nothing. Submit verifications yourself. You control the experience end to end and choose which method to use for each customer. ## Verifying through the widget If you have already embedded the Fluz widget, you may not need to build a verification flow at all. The widget includes verification as a gate: when a customer who is not yet verified enters it, the widget walks them through verification and then returns them to whatever they were doing. This is the lowest-effort path, and the only one where identity data never touches your systems. See [Verify by Widget](/verify-customers-by-widget). ## Verifying through the API If you are integrating directly against the API, you submit verifications yourself. There are three methods available today, and they differ in what you collect from the customer. You collect nothing. Fluz resolves and verifies the customer's identity from data already on file with `verifyUserPrefillInformation`. You collect the customer's legal name, address, date of birth, and SSN, then submit it with `verifyUserInformation` for an immediate decision. You request a verification link with `requestDocumentVerificationLink`. Fluz returns a hosted URL; your customer uploads their government ID and a selfie to Fluz directly. ### Choosing between the API methods Most integrations escalate only when the lower-friction attempt does not succeed: It is a single synchronous call with no fields to collect, and it runs once per customer. Use it as your default first attempt. If Autofill declines, and you already hold — or can reasonably ask for — the customer's identity details, submit them directly for another immediate decision. If SSN verification also declines, request a verification link and have the customer upload their ID and a selfie. This is the higher-assurance path and resolves asynchronously. A customer only needs to pass once, and all methods share a single verification state. Once a customer reaches `APPROVED` — by any method, including through the widget — further attempts are rejected with an `ERROR` status. Each method has a recipe you can walk through right here, without leaving the page: There is no input to collect — the customer is identified entirely by the access token. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const VERIFY_USER_PREFILL_INFORMATION = gql` mutation verifyUserPrefillInformation { verifyUserPrefillInformation { status message } } `; ``` Authenticate with a [user access token](/recipes/generate-user-access-token) generated for the customer being verified. ```javascript theme={null} const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql'; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); ``` The decision comes back in the same response. ```javascript theme={null} const response = await client.request(VERIFY_USER_PREFILL_INFORMATION); console.log(response); ``` ```json Response theme={null} { "data": { "verifyUserPrefillInformation": { "status": "APPROVED", "message": "User verification successful" } } } ``` Copy-and-run version: [Request KYC Autofill](/recipes/verify-user-autofill). Full field reference: [KYC Autofill](/verify-customers-by-autofill). Pass the user information to be verified. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const VERIFY_USER_INFORMATION = gql` mutation verifyUserInformation($input: VerifyUserInformationInput!) { verifyUserInformation(input: $input) { status message } } `; ``` Authenticate with a [user access token](/recipes/generate-user-access-token) generated for the customer being verified. ```javascript theme={null} const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql'; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); ``` The decision comes back in the same response. ```javascript theme={null} const response = await client.request(VERIFY_USER_INFORMATION, { input: { firstName: 'John', lastName: 'Smith', streetLine1: '123 Main St', streetLine2: '', city: 'Los Angeles', state: 'CA', postalCode: '91234', country: 'United States', dateOfBirth: '01/28/1975', ssnLast4: '1234', }, }); console.log(response); ``` ```json Response theme={null} { "data": { "verifyUserInformation": { "status": "APPROVED", "message": "User verification successful" } } } ``` Copy-and-run version: [Request a User KYC Verification](/recipes/verify-user-kyc). Full field reference: [Verify by SSN](/verify-customers-by-ssn). The customer is identified by the access token, so the input only carries consent and prefill flags. ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const REQUEST_DOC_VERIFICATION = gql` mutation RequestDocumentVerificationLink($input: RequestDocumentVerificationLinkInput!) { requestDocumentVerificationLink(input: $input) { userId verificationType verificationId verificationUrl status message } } `; ``` Authenticate with a [user access token](/recipes/generate-user-access-token) generated for the customer being verified. ```javascript theme={null} const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql'; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); ``` Deliver the returned `verificationUrl` to your customer, and store the `verificationId` to reconcile the webhook that carries the outcome. ```javascript theme={null} const response = await client.request(REQUEST_DOC_VERIFICATION, { input: { gaveConsent: true, prefillData: true, }, }); console.log(response); ``` ```json Response theme={null} { "data": { "requestDocumentVerificationLink": { "userId": "eb910e93-5e39-4f53-99b9-0b033dd8e54b", "verificationType": "DOCUMENT_VERIFICATION", "verificationId": "idv_9MpDJC8aotDaxw", "verificationUrl": "https://verify.fluz.app/idv/idv_9MpDJC8aotDaxw?key=27f09ec042881c2c56945680c53108a4", "status": "OK", "message": "Verification link request successful" } } } ``` Copy-and-run version: [Request a Document Verification Link](/recipes/request-document-verification-link). Full field reference: [Verify by Documents](/verify-customers-by-documents). ## Required scope Every verification method requires the **`VERIFY_KYC`** scope, which allows your application to request identity verification on a customer's behalf. `VERIFY_KYC` is not self-serve. It has to be enabled on your application by Fluz — reach out to your Fluz contact to have it turned on before you begin building. There are two layers of permission, and you need both: | Layer | What it is | How it is granted | | :--------------------- | :---------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------- | | Application scope | Your app is permitted to request verifications at all. | Enabled on your application by Fluz. | | Customer authorization | An individual customer permits *your* app to submit a verification on their behalf. | Granted by the customer during the OAuth flow. Widget apps request it automatically. | The scope must also be included when you [generate the user access token](/recipes/generate-user-access-token) you use for the call. See [Application Scopes](/fluz-dashboard/application-scopes) for the full catalog. ## Set up a webhook Register a webhook endpoint before you send your first verification. Verification is not always resolved inside the API response — document verification in particular completes whenever the customer chooses to finish it, which may be minutes or days after you request the link. Widget verifications complete outside your application entirely. Webhooks are how you learn the outcome. Do not poll for verification status, and do not treat the absence of a webhook as a decline. Register an endpoint and react to the event. See [Webhooks](/fluz-dashboard/webhooks) for endpoint requirements, signature verification, retry behavior, and payload formats. In short: 1. Register an HTTPS endpoint on your application in the Developer Portal. 2. Subscribe to the identity verification events. 3. Verify the `X-HMAC-Signature` header against the raw request body on every delivery. 4. Deduplicate on `X-Event-ID` and respond `2xx` within 30 seconds. Your application — and, for OAuth and widget apps, the individual customer's grant — must hold `VERIFY_KYC` to receive verification events. ## Verification statuses Every method resolves to one of the following statuses. | Status | Message | Description | | :---------- | :--------------------------------------- | :------------------------------------------------------------------------- | | `APPROVED` | User verification successful | The customer is KYC verified. | | `DECLINED` | Verification declined | The customer is not KYC verified. You may escalate to another method. | | `DUPLICATE` | Duplicate user verification | The customer is verified, but their SSN matches an existing Fluz customer. | | `ERROR` | Error encountered with user verification | The request could not be processed. See [Attempt limits](#attempt-limits). | `DUPLICATE` is determined by **SSN only**, not by address — customers legitimately have multiple addresses over time. Fluz does not disclose which other customer matched. An `ERROR` status is returned when the customer is already verified, when the verification attempt limit has been reached, or when the document verification limit has been reached. ## Attempt limits Verification attempts are capped to prevent customers from guessing their way to an approval. * A customer may attempt SSN verification up to **3 times** per user ID through the API. * Document verification requests are capped separately. * **KYC Autofill runs once per customer**, whether it approves or declines. * Once a customer is `APPROVED`, no further attempts are accepted. ## Address formatting Every method that accepts an address expects the customer's **residential** address in the structured fields, with a consistent city, state, and postal code. A malformed or mismatched address is a common cause of an otherwise valid customer being `DECLINED`. PO boxes are not accepted as a legal address and will cause verification to fail. Submit a physical street address. International addresses are accepted. See [Address Formatting Requirements](/concepts/address-formatting-requirements) for the full rules. ## Testing Use the staging environment and the published test identities to exercise each path, including deliberate declines, before going live. See [Testing KYC Flows](/test-kyc-flows). # Overview Source: https://docs.fluz.app/user-registration Create a Fluz account for one of your users over the API, handle the already-exists case correctly, and hand off to verification. `registerUser` provisions a Fluz account for someone using profile data you already hold — no redirect, no hosted form, no asking the user to re-type their name and date of birth. **Restricted access.** This mutation requires explicit permission from Fluz. Contact your account manager to enable user registration for your application. Calls from an application without it fail with `AUTH-0022`. *** ## Where this fits Registration is one step of an onboarding arc, and it's optional — you can hand the whole thing to a widget instead. | Approach | Registration | KYC | Consent | Build cost | | :----------- | :------------- | :---------------------- | :----------------- | :---------------------------- | | **Widget** | Hosted by Fluz | Hosted by Fluz | Hosted by Fluz | Hours | | **Hybrid** | `registerUser` | `verifyUserInformation` | Widget or redirect | Days | | **API only** | `registerUser` | `verifyUserInformation` | OAuth redirect | Days, plus you handle the PII | Register users yourself when you **already hold clean profile data** and don't want the user typing it twice. If you'd be collecting name, date of birth, and contact details purely to pass them to Fluz, use an [embedded widget](/developers/widgets) instead — it keeps that collection inside Fluz's compliance scope. The full sequence for the API path: `registerUser` with name, phone, email, and date of birth. Run KYC with `verifyUserInformation`, or hand the user a document verification link. → [User KYC Verification](/user-kyc-verification) The user grants your application scopes. → [Client-facing OAuth grant flow](/client-facing-o-auth-grant-flow) Mint a user access token and use the API on their behalf. → [Authentication](/concepts/authentication) *** ## The mutation ```graphql theme={null} mutation RegisterUser( $firstName: String! $lastName: String! $phoneNumber: String! $regionCode: String! $emailAddress: String! $dateOfBirth: String! $billingAddress: VirtualCardBillingAddressInput! $acceptCardholderAgreement: Boolean! ) { registerUser( firstName: $firstName lastName: $lastName phoneNumber: $phoneNumber regionCode: $regionCode emailAddress: $emailAddress dateOfBirth: $dateOfBirth billingAddress: $billingAddress acceptCardholderAgreement: $acceptCardholderAgreement ) { success userId accountId billingAddressId error { code message } } } ``` `error` is an object (`RegisterUserError`), not a string — always select `code` and `message` as subfields. Requesting bare `error` won't compile. ### Parameters | Parameter | Type | Required | Description | | :-------------------------- | :----------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `firstName` | String | Yes | The user's first name | | `lastName` | String | Yes | The user's last name | | `phoneNumber` | String | Yes | Digits and `-` accepted — `"5551234567"` or `"555-123-4567"` | | `regionCode` | String | Yes | ISO 3166-1 alpha-2 country code for the phone number, e.g. `"US"` | | `emailAddress` | String | Yes | Must not already exist on a Fluz account | | `dateOfBirth` | String | Yes | `YYYY-MM-DD` | | `billingAddress` | VirtualCardBillingAddressInput | Yes | The user's billing address (`streetAddressLine1`, `city`, `state`, `postalCode`, `country`). Validated, saved for the user's virtual cards, and returned as `billingAddressId` | | `acceptCardholderAgreement` | Boolean | Yes | Must be `true` — registration is rejected otherwise | | `deferSeatAssignment` | Boolean | No | When `true`, creates the user without a rewards-network seat; one is assigned later at card redemption | Names should match the identity documents the user will verify with — a mismatch surfaces later as a KYC failure that's much harder to diagnose than a registration error. ### What gets created A complete Fluz account: the user record, their wallet and balances, and their rewards eligibility. There's nothing further to provision before the account can be verified and used. *** ## Handling the response **Failures come back in `data`, not in `errors`.** A failed registration is an HTTP 200 with `success: false`. Code that only checks the GraphQL `errors` array will read every failure as a success. ```json Success theme={null} { "data": { "registerUser": { "success": true, "error": null } } } ``` ```json Failure theme={null} { "data": { "registerUser": { "success": false, "error": { "code": "AUTH-0026", "message": "The phone number you chose is already in use." } } } } ``` A handler that gets all three layers right: ```typescript theme={null} const res = await fetch(FLUZ.graphqlUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify({ query: REGISTER_USER, variables: profile }), }); const body = await res.json(); // 1. Transport and GraphQL-level failures if (body.errors?.length) { throw new Error(`GraphQL error: ${body.errors[0].message}`); } const result = body.data.registerUser; // 2. Business-level failure — still an HTTP 200 if (!result.success) { switch (result.error.code) { case 'AUTH-0026': case 'AUTH-0027': // Not a failure. This person already has a Fluz account. return routeToAuthorization(profile); case 'AUTH-0004': return promptForValidPhoneNumber(); case 'AUTH-0022': case 'AUTH-0030': // Configuration problem on your side — don't retry, alert. throw new ConfigurationError(result.error.code); default: throw new RetryableError(result.error.message); } } // 3. Success return proceedToVerification(profile); ``` *** ## "Already in use" is a routing signal, not an error `AUTH-0026` (phone) and `AUTH-0027` (email) mean the person **already has a Fluz account**. That's a normal, expected outcome — Fluz accounts are not scoped to your application, so anyone who has used Fluz before, through any app or the consumer product, already exists. Treating this as a failure is the most common integration mistake here. The right response is to stop trying to create an account and start asking for access to the existing one: send the user through the [OAuth grant flow](/client-facing-o-auth-grant-flow), or open a [widget](/developers/widgets). They log in to the account they already have and authorize you. Design your onboarding so the register-then-fall-back path is the normal case rather than an exception branch, and it stays clean at scale. *** ## Retries and duplicates `registerUser` takes **no idempotency key**. A retry is a genuinely new attempt, and a timeout is an unknown outcome. If a call times out or the connection drops, the registration may well have succeeded. Retrying the identical request then returns `AUTH-0026` — which is indistinguishable from the user having had an account all along. That ambiguity is harmless as long as you treat both the same way: **on timeout, retry once, and route `AUTH-0026` / `AUTH-0027` to authorization rather than to an error state.** Either the account you just made or the account that already existed ends up authorized, which is the outcome you wanted. What you must not do is surface "phone number already in use" to a user who just gave you their number for the first time. *** ## After registration A registered account is not yet a verified one. Before the user can move money you need: 1. **Identity verification.** Pass the SSN and address you hold to `verifyUserInformation`, or issue a document verification link for the user to complete. → [User KYC Verification](/user-kyc-verification) 2. **An authorization grant.** Registering someone doesn't give you permission to act for them — that's a separate, explicit step. → [Client-facing OAuth grant flow](/client-facing-o-auth-grant-flow) 3. **Your own identifier attached.** Pass `external_id` on the authorization so you can address this person by your own user ID from then on. → [Managing External Reference IDs](/managing-external-reference-ids) *** ## Handling the data You're transmitting full name, date of birth, email, and phone number — a set that identifies a real person. Send it over TLS from your server, keep it out of logs and error-tracking payloads, and don't echo it back in client-visible responses. Note that date of birth in particular is regulated identity data in most jurisdictions, and it stays sensitive on your side after the call succeeds. If you'd rather not hold any of it, that's the argument for the [widget](/developers/widgets) — Fluz collects it inside its own compliance scope and you never touch it. *** ## Error codes | Code | Meaning | What to do | | :---------- | :------------------------------------------------------------- | :------------------------------------------------------------- | | `AUTH-0004` | Phone number invalid or unparseable for the given `regionCode` | Fix the input. Check `regionCode` matches the number's country | | `AUTH-0022` | Your application isn't permitted to register users | Contact your account manager. Don't retry | | `AUTH-0025` | General registration failure | Retryable. Escalate if it persists | | `AUTH-0026` | Phone number already on an account | Route to authorization — see above | | `AUTH-0027` | Email already on an account | Route to authorization — see above | | `AUTH-0030` | Your application is not active | Check the app's status in the dashboard. Don't retry | *** ## Environments Point at the GraphQL host for your environment — `https://transactional-graph.staging.fluzapp.com/api/v1/graphql` for staging, `https://transactional-graph.fluzapp.com/api/v1/graphql` for production. Registration permission is granted per application, so a production app needs it enabled separately. → [Deploying to Production](/deploying-to-production) Never register real people in staging. → [Staging vs. Live](/concepts/environments) *** ## Next steps Verify the account you just created. Get permission to act on their behalf. Address them by your own user ID. Hand the whole onboarding to Fluz instead. # KYC Autofill Source: https://docs.fluz.app/verify-customers-by-autofill Request an identity decision using data Fluz already has on file for the customer — no identity fields to collect or submit. KYC Autofill is the lowest-effort API verification method. You don't collect or submit any identity fields — Fluz resolves and verifies the customer's identity using data already on file (such as their registered phone number) and returns a decision in the same response. Use it as your first API attempt whenever you want to avoid collecting identity data from the customer at all. If it declines, fall back to [SSN verification](/verify-customers-by-ssn) or [requesting an IDV URL](/verify-customers-by-documents). **Prerequisites** * The `VERIFY_KYC` scope enabled on your application by Fluz. See [Required scope](/user-kyc-verification#required-scope). * A [user access token](/recipes/generate-user-access-token) generated for the customer being verified, including `VERIFY_KYC` in its scopes. * A registered webhook endpoint. See [Verify Customers](/user-kyc-verification#set-up-a-webhook). ## How it works The `verifyUserPrefillInformation` mutation is synchronous. The customer is identified entirely by the user access token — there is no input to collect or validate. Fluz screens the customer's data on file and returns `APPROVED`, `DECLINED`, `DUPLICATE`, or `ERROR` in the response body. Fluz also emits a verification event to your webhook endpoint, so a single handler can process outcomes from every verification method consistently. KYC Autofill runs once per customer, whether it approves or declines — a repeat call returns without re-attempting verification. ## Request The customer being verified is identified by the user access token in the `Authorization` header. There is no input to provide. ## Example ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql'; const VERIFY_USER_PREFILL_INFORMATION = gql` mutation verifyUserPrefillInformation { verifyUserPrefillInformation { status message } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(VERIFY_USER_PREFILL_INFORMATION); console.log(response); ``` ```json Response theme={null} { "data": { "verifyUserPrefillInformation": { "status": "APPROVED", "message": "User verification successful" } } } ``` ## Handling the response | Status | What to do | | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `APPROVED` | The customer is verified. Unlock the relevant functionality. | | `DECLINED` | Escalate to [SSN verification](/verify-customers-by-ssn) or [document verification](/verify-customers-by-documents). | | `DUPLICATE` | The customer is verified, but their identity data matches another Fluz customer. Treat as verified and review for duplicate accounts on your side. Fluz does not disclose which customer matched. | | `ERROR` | Inspect `message`. The customer is either already verified or has exhausted their attempts. | ## Testing See [Testing KYC Flows](/test-kyc-flows) for the general staging setup — a developer account, an active application, and a user access token for the customer you're verifying. Deterministic staging test identities for KYC Autofill aren't published yet. Confirm expected outcomes with the platform team before relying on them in an automated test suite. # Verify by Documents Source: https://docs.fluz.app/verify-customers-by-documents Request a hosted verification link, deliver it to your customer, and let them upload their government ID and a selfie. Requesting an IDV URL is the higher-assurance API method, and the fallback when [passing us the SSN information](/verify-customers-by-ssn) does not produce an approval. Rather than collecting identity documents yourself, you request a hosted verification link from Fluz and hand it to your customer. Fluz collects and screens the documents directly. **Prerequisites** * The `VERIFY_KYC` scope enabled on your application by Fluz. See [Required scope](/user-kyc-verification#required-scope). * A [user access token](/recipes/generate-user-access-token) generated for the customer being verified, including `VERIFY_KYC` in its scopes. * A registered webhook endpoint. **This is required for document verification** — the outcome is not available in the API response. See [Verify Customers](/user-kyc-verification#set-up-a-webhook). ## How it works Call `requestDocumentVerificationLink` using a user access token generated for that customer. The token is what tells Fluz which customer the verification belongs to. The response contains a `verificationUrl` and a `verificationId`. Store the `verificationId` — it is how you reconcile the eventual webhook with this request. Send it however suits your product: email, SMS, push notification, or an in-app redirect. The customer can complete verification at any time. On the hosted page, the customer captures the front and back of a government-issued ID (driver's license, passport, state ID, or military ID) and takes a selfie for a biometric match. When the customer finishes, Fluz screens the submission and sends an `APPROVED` or `DECLINED` result to your webhook endpoint. The verification link is unique to one customer and one verification attempt. Never reuse a link across customers, log it in a shared system, or expose it anywhere the intended customer is not the only reader — it grants access to an identity submission session. ## Request | Field | Type | Required | Description | | :------------- | :------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | | `gaveConsent` | Boolean | Yes | Whether the customer consented to identity verification. Must be `true`, or the request is rejected. | | `prefillData` | Boolean | Yes | Whether to prefill the verification form with the identity information Fluz already holds. When `true`, you do not need to send the fields below. | | `firstName` | String | No | The customer's legal first name. | | `lastName` | String | No | The customer's legal last name. | | `streetLine1` | String | No | Residential street address. | | `streetLine2` | String | No | Apartment, suite, or unit. | | `city` | String | No | City. | | `region` | String | No | State or region. | | `postalCode` | String | No | ZIP or postal code. | | `country` | String | No | Country, in ISO 3166-1 alpha-2 format. | | `dateOfBirth` | String | No | Date of birth, formatted `YYYY-MM-DD`. | | `emailAddress` | String | No | The customer's email address. | | `phoneNumber` | String | No | The customer's phone number in E.164 format. | You must capture and record the customer's consent before setting `gaveConsent: true`. Anything you pass is used to prefill the form — the customer can review and correct it before submitting, so treat these fields as a convenience, not as the values that will be verified. Note the field naming difference from [SSN verification](/verify-customers-by-ssn): this mutation uses `region` where the other uses `state`, and expects `dateOfBirth` as `YYYY-MM-DD` rather than `MM/DD/YYYY`. ## Example ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql'; const REQUEST_DOC_VERIFICATION = gql` mutation RequestDocumentVerificationLink($input: RequestDocumentVerificationLinkInput!) { requestDocumentVerificationLink(input: $input) { userId verificationType verificationId verificationUrl status message } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(REQUEST_DOC_VERIFICATION, { input: { gaveConsent: true, prefillData: true, }, }); console.log(response); ``` ```json Response theme={null} { "data": { "requestDocumentVerificationLink": { "userId": "eb910e93-5e39-4f53-99b9-0b033dd8e54b", "verificationType": "DOCUMENT_VERIFICATION", "verificationId": "idv_9MpDJC8aotDaxw", "verificationUrl": "https://verify.fluz.app/idv/idv_9MpDJC8aotDaxw?key=27f09ec042881c2c56945680c53108a4", "status": "OK", "message": "Verification link request successful" } } } ``` The `status` in this response (`OK`) confirms only that the **link was issued** — it is not the verification decision. The decision arrives later, by webhook. A copy-and-run version of this example, ready to adapt to your integration. ## Response fields | Field | Type | Description | | :----------------- | :----- | :--------------------------------------------------------------------------- | | `userId` | UUID | The Fluz customer the verification belongs to. | | `verificationType` | String | Always `DOCUMENT_VERIFICATION`. | | `verificationId` | String | Identifier for this verification attempt. Store it to reconcile the webhook. | | `verificationUrl` | String | The hosted link to deliver to your customer. | | `status` | String | `OK` when the link was issued successfully. | | `message` | String | Human-readable detail. | ## What the customer sees The hosted flow prefills known information for the customer to review, then asks them to: 1. Capture the front and back of a supported government-issued photo ID. 2. Take a selfie, which is matched biometrically against the ID photo. Document authenticity, tampering, and the biometric match are all screened before a decision is returned. ## Receiving the outcome Because the customer may complete verification long after you issue the link, the outcome arrives at your webhook endpoint. Match the incoming event to your stored `verificationId`, then act on the result: * **`APPROVED`** — the customer is verified. Unlock the relevant functionality. * **`DECLINED`** — the customer is not verified. Document verification is the final step in the escalation path; a decline here generally requires manual review rather than another automated attempt. Document verification requests are capped per customer. Once the limit is reached, further requests return `ERROR` with `Exceeded document verification limit`. ## Testing In staging you can complete the full document flow using sample driver's licenses and test identities. Document authenticity and other security features are not enforced in staging, and you may reuse the front image for the back capture. See [Testing KYC Flows](/test-kyc-flows). # Verify by SSN Source: https://docs.fluz.app/verify-customers-by-ssn Submit a customer's legal name, address, date of birth, and last four SSN digits for an immediate identity decision. Passing Fluz the SSN information is the more direct of the two API methods. You collect a small set of identity fields from the customer and submit them, and Fluz returns a decision in the same response. Use it as your first API attempt whenever you already hold — or can reasonably ask for — the customer's identity details. If it declines, escalate to [requesting an IDV URL](/verify-customers-by-documents). **Prerequisites** * The `VERIFY_KYC` scope enabled on your application by Fluz. See [Required scope](/user-kyc-verification#required-scope). * A [user access token](/recipes/generate-user-access-token) generated for the customer being verified, including `VERIFY_KYC` in its scopes. * A registered webhook endpoint. See [Verify Customers](/user-kyc-verification#set-up-a-webhook). ## How it works The `verifyUserInformation` mutation is synchronous. You submit the customer's information and Fluz screens it against identity data on file, returning `APPROVED`, `DECLINED`, `DUPLICATE`, or `ERROR` in the response body. There is no customer-facing step and nothing for the customer to complete. Fluz also emits a verification event to your webhook endpoint, so a single handler can process outcomes from every verification method consistently. ## Request The customer being verified is identified by the user access token in the `Authorization` header — you do not pass a user ID in the input. | Field | Type | Required | Description | | :------------ | :----- | :------- | :--------------------------------------------------------- | | `firstName` | String | Yes | The customer's legal first name. | | `lastName` | String | Yes | The customer's legal last name. | | `streetLine1` | String | Yes | Residential street address. | | `streetLine2` | String | No | Apartment, suite, or unit. Pass an empty string if unused. | | `city` | String | Yes | City. | | `state` | String | Yes | State or region. | | `postalCode` | String | Yes | ZIP or postal code. | | `country` | String | Yes | Country. | | `dateOfBirth` | String | Yes | Date of birth, formatted `MM/DD/YYYY`. | | `ssnLast4` | String | Yes | The last four digits of the customer's SSN. | Fluz accepts either the full SSN or just the last four digits. Submitting only the last four is recommended — it produces the same decision while reducing what you have to collect and store. Submit the customer's **residential** address, not a billing or mailing address. PO boxes are rejected. A mismatched address is the most common cause of a false decline — see [Address Formatting Requirements](/concepts/address-formatting-requirements). ## Example ```javascript theme={null} import { GraphQLClient, gql } from 'graphql-request'; const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql'; const VERIFY_USER_INFORMATION = gql` mutation verifyUserInformation($input: VerifyUserInformationInput!) { verifyUserInformation(input: $input) { status message } } `; const client = new GraphQLClient(API_URL, { headers: { Authorization: `Bearer <>`, 'Content-Type': 'application/json', }, }); const response = await client.request(VERIFY_USER_INFORMATION, { input: { firstName: 'John', lastName: 'Smith', streetLine1: '123 Main St', streetLine2: '', city: 'Los Angeles', state: 'CA', postalCode: '91234', country: 'United States', dateOfBirth: '01/28/1975', ssnLast4: '1234', }, }); console.log(response); ``` ```json Response theme={null} { "data": { "verifyUserInformation": { "status": "APPROVED", "message": "User verification successful" } } } ``` A copy-and-run version of this example, ready to adapt to your integration. ## Handling the response | Status | What to do | | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `APPROVED` | The customer is verified. Unlock the relevant functionality. | | `DECLINED` | Escalate to [document verification](/verify-customers-by-documents). Do not re-submit the same information. | | `DUPLICATE` | The customer is verified, but their **SSN** matches another Fluz customer. Treat as verified and review for duplicate accounts on your side. Fluz does not disclose which customer matched. | | `ERROR` | Inspect `message`. The customer is either already verified or has exhausted their attempts. | A customer may attempt SSN verification up to **3 times**. After the third attempt, further requests return `ERROR` with `Exceeded user verification limit`. Move the customer to document verification rather than retrying. ## Testing Staging test identities return specific, deterministic result codes so you can exercise declines — address mismatch, deceased subject, thin file, invalid SSN, and others — without using real data. See [Testing KYC Flows](/test-kyc-flows). Do not modify the test identity data. Any field that does not match the expected test values will return a no-match rather than the result code you are trying to exercise. # Verify by Widget Source: https://docs.fluz.app/verify-customers-by-widget Let the Fluz widget handle identity verification as a built-in step, with no identity data passing through your systems. If you embed the Fluz widget, verification comes with it. You do not build a verification flow, collect identity fields, or handle documents — the widget presents verification as a gate and clears it before letting the customer continue. This is the only path where a customer's identity information never touches your infrastructure, which is usually the deciding factor in choosing it. **Prerequisites** * A widget application configured in the Developer Portal, with OAuth settings in place. See [Configure App Widget](/developers/configure-app-widget). * The `VERIFY_KYC` scope enabled on your application by Fluz. See [Required scope](/user-kyc-verification#required-scope). * A registered webhook endpoint. See [Verify Customers](/user-kyc-verification#set-up-a-webhook). ## Where verification fits Verification is a step in the widget's normal sequence, not a separate integration: The widget opens in an iframe. If they do not yet have a Fluz account, the widget creates one. If the customer is already verified, it moves straight on. If not, verification runs here. PIN setup and OAuth scope authorization. Confirmation, then completion. Because verification sits ahead of the transaction, a customer who cannot verify will not reach the transaction step at all. ## What the customer experiences When an unverified customer reaches the gate, the widget first attempts to verify them silently in the background. Many customers clear the gate at this point without being asked for anything. If that does not resolve, the widget presents a verification form inside the iframe, prefilled with whatever Fluz already holds. The customer reviews it, supplies what is missing, and submits. The widget then shows a waiting state while the result is processed and advances automatically once it resolves. If verification still does not succeed, the widget escalates the customer to document verification — capturing their government-issued ID and a selfie — within the same iframe. All of this happens inside the widget. You do not need to detect which stage a customer is at or trigger the escalation yourself. ## Scopes Widget applications request the scopes they need automatically during the OAuth authorization step, including `VERIFY_KYC`. You do not have to add it to the widget's scope list manually. You do still need `VERIFY_KYC` enabled on the application itself by Fluz. If it is not, the widget will fail to load with a missing-permissions error rather than skipping verification. When generating the short-lived token you pass to the widget as `patToken`, use your **API Key** for the `Authorization: Basic` header — not your OAuth client ID and not your `app_id`. Using the wrong value is the most common cause of a widget failing to load. See [Obtain Your API Credentials](/get-started/api-credentials). ## Knowing the outcome The widget tells the customer their result directly, but your application should not infer verification state from the widget closing. Rely on webhooks instead. Fluz emits `WIDGET_KYC_INITIATION` when a customer **begins** verification in the widget. This requires the `VERIFY_KYC` scope. ```json theme={null} { "eventType": "WIDGET_KYC_INITIATION", "userId": "550e8400-e29b-41d4-a716-446655440000", "accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "externalReferenceId": "your-reference-123" } ``` This event marks the **start** of verification, not the result. Use `externalReferenceId` to map the event to your own customer record, and subscribe to the verification outcome events as well so you know how it resolved. See [Webhooks](/fluz-dashboard/webhooks). ## Combining the widget with the API The widget and the API share one verification state per customer, so the two approaches compose cleanly: * A customer verified through the API will pass straight through the widget's gate. * A customer verified in the widget is verified for your API calls too. * A customer who has exhausted their attempts in one channel has exhausted them in both. This matters if you verify customers through the API during onboarding and later hand them to the widget: check the customer's current verification status rather than assuming the widget will offer them another attempt. ## Testing Run the full widget flow in staging against the published test identities, including a deliberate decline so you can see the escalation to document verification. Staging widgets point at the staging environment rather than production — confirm you are loading the staging widget script and base URL. See [Testing KYC Flows](/test-kyc-flows) and [Staging vs. Live Environment](/concepts/environments). # WorldPay Source: https://docs.fluz.app/world-pay Email sales to request access to our BETA program of Payment Processing API connectors. # addAuthorizedUser Source: https://docs.fluz.app/api-reference/mutations/add-authorized-user Add an authorized user to the caller's account with specified UAC roles. Add an authorized user to the caller's account with specified UAC roles. The target account is always resolved from the caller's credentials - Bearer tokens use the token's accountId; Basic (API key) callers use the application's configured operator account. Requires the MANAGE\_SUBUSERS scope. Supports both Bearer and Basic auth. The target user must already exist in Fluz. If the user previously had a DECLINED or INACTIVE assignment, it will be reactivated with the new roles. ```graphql theme={null} mutation { addAuthorizedUser( email: String phone: String roles: [UACRoleType!]! status: UACRoleStatusType sendInvite: Boolean! ): AddAuthorizedUserResponse } ``` ## Arguments The email address of the user to add. At least one of email or phone is required. The phone number of the user to add. At least one of email or phone is required. The UAC roles to assign. OWNER is not allowed. The initial status for the role assignment. Defaults to PENDING. Whether auth-service should send the role assignment invite email. Defaults to true. Set to false to create the assignment without sending an invite. ## Returns [`AddAuthorizedUserResponse`](/api-reference/types/add-authorized-user-response) — Response returned from the addAuthorizedUser mutation. # addBankCard Source: https://docs.fluz.app/api-reference/mutations/add-bank-card addBankCard adds a bank card to the user's wallet. addBankCard adds a bank card to the user's wallet. Requires PCI\_COMPLIANCE from the developer and MANAGE\_PAYMENT scope. PERSONAL/Private applications are exempt from PCI\_COMPLIANCE requirement. ```graphql theme={null} mutation { addBankCard( input: AddBankCardInput! ): BankCard } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`BankCard`](/api-reference/types/bank-card) — BankCard is a record of a bank card added by a user. # addVirtualCardAddress Source: https://docs.fluz.app/api-reference/mutations/add-virtual-card-address Saves a billing address for virtual card issuance. Saves a billing address for virtual card issuance. Requires CREATE\_VIRTUALCARD scope. When authUserId is provided, the address is saved for that authorized user's underlying user\_id but remains attached to the caller's account. ```graphql theme={null} mutation { addVirtualCardAddress( input: AddVirtualCardAddressInput! ): UserAddress } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`UserAddress`](/api-reference/types/user-address) — UserAddress represents a User Address record. # approveApprovalRequest Source: https://docs.fluz.app/api-reference/mutations/approve-approval-request Approves an open approval request through the existing approval link pipeline. Approves an open approval request through the existing approval link pipeline. ```graphql theme={null} mutation { approveApprovalRequest( approvalId: UUID! ): ApprovalRequestActionResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`ApprovalRequestActionResponse!`](/api-reference/types/approval-request-action-response) — Response returned from approve/decline approval request mutations. # assignObjectOwner Source: https://docs.fluz.app/api-reference/mutations/assign-object-owner Assign an owner to an object that does not have one yet. Assign an owner to an object that does not have one yet. The target account is resolved from the caller's credentials (Bearer accountId or Basic operator account). Requires MANAGE\_SUBUSERS. Forwards to auth-service POST /api/v1/object-owners. ```graphql theme={null} mutation { assignObjectOwner( objectType: ObjectOwnerObjectType! objectId: UUID! userId: UUID! ): AssignObjectOwnerResponse } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`AssignObjectOwnerResponse`](/api-reference/types/assign-object-owner-response) # assignSupervisor Source: https://docs.fluz.app/api-reference/mutations/assign-supervisor *No description provided in the schema yet.* ```graphql theme={null} mutation { assignSupervisor( superviseeUserId: UUID! supervisorUserId: UUID! ): SupervisionAssignmentResponse! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`SupervisionAssignmentResponse!`](/api-reference/types/supervision-assignment-response) # bulkUpdateErpTransactionMetadata Source: https://docs.fluz.app/api-reference/mutations/bulk-update-erp-transaction-metadata Applies ERP-aware categorization to multiple transactions in one call (max 100 items). Applies ERP-aware categorization to multiple transactions in one call (max 100 items). ```graphql theme={null} mutation { bulkUpdateErpTransactionMetadata( items: [BulkUpdateErpTransactionMetadataItem!]! ): BulkUpdateErpTransactionMetadataResult! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`BulkUpdateErpTransactionMetadataResult!`](/api-reference/types/bulk-update-erp-transaction-metadata-result) # closeUserCashBalance Source: https://docs.fluz.app/api-reference/mutations/close-user-cash-balance Mutation for closing a user cash balance account. Mutation for closing a user cash balance account. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { closeUserCashBalance( input: CloseUserCashBalanceInput! ): CloseUserCashBalanceResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`CloseUserCashBalanceResponse!`](/api-reference/types/close-user-cash-balance-response) — Response type for the closeUserCashBalance mutation, returning a closed user cash balance and affected virtual card records # completePlaidLink Source: https://docs.fluz.app/api-reference/mutations/complete-plaid-link Completes Plaid Link after the frontend receives publicToken from Plaid Link onSuccess. Completes Plaid Link after the frontend receives publicToken from Plaid Link onSuccess. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { completePlaidLink( input: CompletePlaidLinkInput! ): PlaidLinkCompletionResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`PlaidLinkCompletionResponse!`](/api-reference/types/plaid-link-completion-response) — Safe public result of completing a Plaid Link flow. # createPlaidLinkAddress Source: https://docs.fluz.app/api-reference/mutations/create-plaid-link-address Creates the billing/legal address required after Plaid Link when completePlaidLink returns requiresAddress. Creates the billing/legal address required after Plaid Link when completePlaidLink returns requiresAddress. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { createPlaidLinkAddress( input: CreatePlaidLinkAddressInput! ): CreatePlaidLinkAddressResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`CreatePlaidLinkAddressResponse!`](/api-reference/types/create-plaid-link-address-response) # createPlaidLinkToken Source: https://docs.fluz.app/api-reference/mutations/create-plaid-link-token Creates a Plaid Link token for new bank linking or relinking an existing Plaid connection. Creates a Plaid Link token for new bank linking or relinking an existing Plaid connection. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { createPlaidLinkToken( input: CreatePlaidLinkTokenInput! ): PlaidLinkTokenResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`PlaidLinkTokenResponse!`](/api-reference/types/plaid-link-token-response) — Response containing the token required to initialize Plaid Link. # createTransfer Source: https://docs.fluz.app/api-reference/mutations/create-transfer Create a transfer between accounts within an application. Create a transfer between accounts within an application. The sender is determined from the authentication credentials. ```graphql theme={null} mutation { createTransfer( input: CreateTransferInput! ): CreateTransferResponse } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`CreateTransferResponse`](/api-reference/types/create-transfer-response) — Represents the response returned from the createTransfer mutation. # createUserCashBalance Source: https://docs.fluz.app/api-reference/mutations/create-user-cash-balance Mutation for creating a new user cash balance account. Mutation for creating a new user cash balance account. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { createUserCashBalance( input: CreateUserCashBalanceInput! ): UserCashBalance! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`UserCashBalance!`](/api-reference/types/user-cash-balance) — UserCashBalance represents a user's cash balance account. # createVirtualCard Source: https://docs.fluz.app/api-reference/mutations/create-virtual-card Initiates virtual card creation. Initiates virtual card creation. Requires CREATE\_VIRTUALCARD scope. @throws INVALID\_INPUT if: * idempotency key is not provided * spendLimit less than \$1 * lockDate is in the past * spendLimit is not within card program limit * offerId is defined and is in uuid format When billingAddress or userAddressId is provided, the address is registered with the card program before card issuance. If the issuer has not approved the address within the server-side wait window (typically \~60s), this mutation returns an error with code VC-0020 (PENDING\_APPROVAL) and extensions.addressId. Callers should retry the same request using the returned addressId until the card is issued successfully. ```graphql theme={null} mutation { createVirtualCard( input: CreateVirtualCardInput! ): VirtualCard } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`VirtualCard`](/api-reference/types/virtual-card) — VirtualCard represents a record of a virtual card generated by a user. # createVirtualCardBulkOrder Source: https://docs.fluz.app/api-reference/mutations/create-virtual-card-bulk-order Initiates bulk virtual card creation. Initiates bulk virtual card creation. Requires CREATE\_VIRTUALCARD scope. As with createVirtualCard, per-item billingAddress or userAddressId triggers card-program registration before job enqueue. A PENDING\_APPROVAL error with extensions.addressId may be returned on first call for a brand-new address; retry the same request to complete. ```graphql theme={null} mutation { createVirtualCardBulkOrder( input: CreateVirtualCardBulkOrderInput! ): VirtualCardBulkOrder } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`VirtualCardBulkOrder`](/api-reference/types/virtual-card-bulk-order) — VirtualCardBulkOrder provides information about bulk creation order. # deactivateVCShareLinks Source: https://docs.fluz.app/api-reference/mutations/deactivate-vcshare-links Deactivates share links. Deactivates share links. Requires CREATE\_SHARE\_LINK scope. ```graphql theme={null} mutation { deactivateVCShareLinks( input: DeactivateVCShareLinksInput! ): String } ``` ## Arguments *No description provided in the schema yet.* ## Returns `String` — The `String` scalar type represents textual data, represented as UTF-8 character sequences. # declineApprovalRequest Source: https://docs.fluz.app/api-reference/mutations/decline-approval-request Declines an open approval request through the existing approval link pipeline. Declines an open approval request through the existing approval link pipeline. ```graphql theme={null} mutation { declineApprovalRequest( approvalId: UUID! ): ApprovalRequestActionResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`ApprovalRequestActionResponse!`](/api-reference/types/approval-request-action-response) — Response returned from approve/decline approval request mutations. # deleteBankCard Source: https://docs.fluz.app/api-reference/mutations/delete-bank-card deleteBankCard set a bank card's status to INACTIVE. deleteBankCard set a bank card's status to INACTIVE. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { deleteBankCard( input: DeleteBankCardInput! ): BankCard } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`BankCard`](/api-reference/types/bank-card) — BankCard is a record of a bank card added by a user. # depositCashBalance Source: https://docs.fluz.app/api-reference/mutations/deposit-cash-balance Mutation for depositing into a cash balance. Mutation for depositing into a cash balance. Requires MAKE\_DEPOSIT scope. ```graphql theme={null} mutation { depositCashBalance( input: DepositCashBalanceInput! ): DepositCashBalanceResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`DepositCashBalanceResponse!`](/api-reference/types/deposit-cash-balance-response) — Response type for the depositCashBalance mutation, returning the resulting deposits and current user balances. # editVirtualCard Source: https://docs.fluz.app/api-reference/mutations/edit-virtual-card Mutation to edit virtual cards. Mutation to edit virtual cards. Requires EDIT\_VIRTUALCARD scope. ```graphql theme={null} mutation { editVirtualCard( input: EditVirtualCardInput! ): VirtualCard } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`VirtualCard`](/api-reference/types/virtual-card) — VirtualCard represents a record of a virtual card generated by a user. # generateUserAccessToken Source: https://docs.fluz.app/api-reference/mutations/generate-user-access-token Generate a user token using 'Basic '. Generate a user token using 'Basic \'. The token will be associated with the user\_id and will have the specified scopes. ```graphql theme={null} mutation { generateUserAccessToken( userId: UUID accountId: UUID scopes: [ScopeType!]! seatId: UUID externalReferenceId: String ): GenerateUserAccessTokenResponse } ``` ## Arguments The userId that will be associated with the token. Required if externalReferenceId is not provided. The accountId that will be associated with the token. Required if externalReferenceId is not provided. The scopes that the token will have access to. The seatId will be used to make transactions, default to the most recently created if not provided. Your unique identifier for this user. This is the same value passed as external\_id during the OAuth authorization flow. Only applicable to OAuth applications. If provided, userId and accountId are optional. ## Returns [`GenerateUserAccessTokenResponse`](/api-reference/types/generate-user-access-token-response) — Represents the response returned from the generateUserAccessToken mutation. # generateVCShareLinks Source: https://docs.fluz.app/api-reference/mutations/generate-vcshare-links Generates share links. Generates share links. Requires CREATE\_SHARE\_LINK scope. Supersedes the removed enrollVirtualCardRecipient mutation (EDIT\_VIRTUALCARD is not required). ```graphql theme={null} mutation { generateVCShareLinks( input: GenerateVCShareLinksInput! ): ShareRequest } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`ShareRequest`](/api-reference/types/share-request) — ShareRequest represents a response from creating a share link. # lockVirtualCard Source: https://docs.fluz.app/api-reference/mutations/lock-virtual-card Mutation to lock virtual cards. Mutation to lock virtual cards. Requires EDIT\_VIRTUALCARD scope. ```graphql theme={null} mutation { lockVirtualCard( input: LockVirtualCardInput! ): LockVirtualCardResponse } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`LockVirtualCardResponse`](/api-reference/types/lock-virtual-card-response) # purchaseGiftCard Source: https://docs.fluz.app/api-reference/mutations/purchase-gift-card Initiates a gift card purchase transaction. Initiates a gift card purchase transaction. Requires PURCHASE\_GIFTCARD scope. The offerId or merchantSlug fields cannot both be empty. If both are provided, offerId will be used. ```graphql theme={null} mutation { purchaseGiftCard( input: PurchaseGiftCardInput! ): UserPurchase } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`UserPurchase`](/api-reference/types/user-purchase) — UserPurchase represents a record of a purchase made by a user, detailing the payment methods and other relevant information. # reassignSupervisor Source: https://docs.fluz.app/api-reference/mutations/reassign-supervisor *No description provided in the schema yet.* ```graphql theme={null} mutation { reassignSupervisor( superviseeUserId: UUID! supervisorUserId: UUID! ): SupervisionAssignmentResponse! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`SupervisionAssignmentResponse!`](/api-reference/types/supervision-assignment-response) # redeemFluzGiftCard Source: https://docs.fluz.app/api-reference/mutations/redeem-fluz-gift-card Mutation for redeeming a Fluz Gift Card by code. Mutation for redeeming a Fluz Gift Card by code. Credits the authenticated user's gift card balance. Requires MAKE\_DEPOSIT scope. ```graphql theme={null} mutation { redeemFluzGiftCard( input: RedeemFluzGiftCardInput! ): DepositCashBalanceResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`DepositCashBalanceResponse!`](/api-reference/types/deposit-cash-balance-response) — Response type for the depositCashBalance mutation, returning the resulting deposits and current user balances. # refreshPlaidBankAccountBalance Source: https://docs.fluz.app/api-reference/mutations/refresh-plaid-bank-account-balance Requests a rate-limited realtime balance refresh for one owned Plaid bank account. Requests a rate-limited realtime balance refresh for one owned Plaid bank account. Requires MANAGE\_PAYMENT scope. Identity-service enforces 1 refresh per hour and 6 refreshes per day per Plaid institution. ```graphql theme={null} mutation { refreshPlaidBankAccountBalance( input: PlaidBankAccountInput! ): PlaidBankAccountBalanceRefreshResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`PlaidBankAccountBalanceRefreshResponse!`](/api-reference/types/plaid-bank-account-balance-refresh-response) — Result of a rate-limited realtime refresh for one Plaid bank account's owning institution. # refreshPlaidBankConnections Source: https://docs.fluz.app/api-reference/mutations/refresh-plaid-bank-connections Requests a cached data refresh for all connected Plaid institutions owned by the authenticated account. Requests a cached data refresh for all connected Plaid institutions owned by the authenticated account. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { refreshPlaidBankConnections: PlaidBankConnectionRefreshResponse! } ``` ## Returns [`PlaidBankConnectionRefreshResponse!`](/api-reference/types/plaid-bank-connection-refresh-response) — Result of a cached refresh for all Plaid connections owned by the authenticated account. # refreshUserAccessToken Source: https://docs.fluz.app/api-reference/mutations/refresh-user-access-token Refresh a user token using 'Basic '. Refresh a user token using 'Basic \'. The token will be associated with the user\_id and will have the specified scopes. ```graphql theme={null} mutation { refreshUserAccessToken( refreshToken: String! ): RefreshUserAccessTokenResponse } ``` ## Arguments The refreshToken issued to get a new access token. ## Returns [`RefreshUserAccessTokenResponse`](/api-reference/types/refresh-user-access-token-response) — Represents the response returned from the generateUserAccessToken mutation. # registerBusiness Source: https://docs.fluz.app/api-reference/mutations/register-business Register a new business. Register a new business. Creates business entity, signup form, and initiates KYB process. Use the REST endpoint POST /api/v1/file-upload/sole-proprietorship-document to upload the document first if business structure is SOLE\_PROPRIETORSHIP. ```graphql theme={null} mutation { registerBusiness( input: RegisterBusinessInput! ): RegisterBusinessResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`RegisterBusinessResponse!`](/api-reference/types/register-business-response) — Response from business registration. # registerUser Source: https://docs.fluz.app/api-reference/mutations/register-user Registration of a user. Registration of a user. Requires special permission from Fluz to enable. Please contact support to enable this feature if needed. ```graphql theme={null} mutation { registerUser( firstName: String! lastName: String! phoneNumber: String! regionCode: String! emailAddress: String! dateOfBirth: String! billingAddress: VirtualCardBillingAddressInput! acceptCardholderAgreement: Boolean! deferSeatAssignment: Boolean ): RegisterUserResponse } ``` ## Arguments The first name of user for registration. The last name of user for registration. The phone number name of user for registration. The region code of phone number for registration. The email of user for registration. The date of birth of user for registration. The user's billing address. Validated and saved as the billing address for the user's virtual cards; its id is returned as billingAddressId. Confirmation that the user has accepted the cardholder agreement. Must be true — registration is rejected otherwise. When true, creates user without a rewards-network seat. Seat is assigned later at card redemption. ## Returns [`RegisterUserResponse`](/api-reference/types/register-user-response) — Represents the response returned from the registerUser mutation. # removeAuthorizedUser Source: https://docs.fluz.app/api-reference/mutations/remove-authorized-user Remove an authorized user from the caller's account by setting their role assignment to INACTIVE. Remove an authorized user from the caller's account by setting their role assignment to INACTIVE. The target account is always resolved from the caller's credentials — Bearer tokens use the token's accountId; Basic (API key) callers use the application's configured operator account. Requires the MANAGE\_SUBUSERS scope. Supports both Bearer and Basic auth. The account owner (OWNER role) cannot be removed. ```graphql theme={null} mutation { removeAuthorizedUser( authUserId: UUID! ): RemoveAuthorizedUserResponse } ``` ## Arguments The authorized user ID (UAC role assignment ID) to deactivate. Obtain this from the authorizedUsers query or the addAuthorizedUser mutation response. ## Returns [`RemoveAuthorizedUserResponse`](/api-reference/types/remove-authorized-user-response) — Response returned from the removeAuthorizedUser mutation. # removePlaidBankInstitution Source: https://docs.fluz.app/api-reference/mutations/remove-plaid-bank-institution Removes a Plaid bank institution and disables its local bank accounts. Removes a Plaid bank institution and disables its local bank accounts. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { removePlaidBankInstitution( input: RemovePlaidBankInstitutionInput! ): RemovePlaidBankInstitutionResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`RemovePlaidBankInstitutionResponse!`](/api-reference/types/remove-plaid-bank-institution-response) # removeSupervisor Source: https://docs.fluz.app/api-reference/mutations/remove-supervisor *No description provided in the schema yet.* ```graphql theme={null} mutation { removeSupervisor( superviseeUserId: UUID! ): SupervisionAssignmentResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`SupervisionAssignmentResponse!`](/api-reference/types/supervision-assignment-response) # requestAccountTransfer Source: https://docs.fluz.app/api-reference/mutations/request-account-transfer Requests manager approval for an account-to-account transfer. Requests manager approval for an account-to-account transfer. ```graphql theme={null} mutation { requestAccountTransfer( input: RequestAccountTransferInput! ): RequestApprovalResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`RequestApprovalResponse!`](/api-reference/types/request-approval-response) — Response returned from request approval mutations. # requestDocumentVerificationLink Source: https://docs.fluz.app/api-reference/mutations/request-document-verification-link Request verification for a user with provided information Request verification for a user with provided information ```graphql theme={null} mutation { requestDocumentVerificationLink( input: RequestDocumentVerificationLinkInput! ): RequestDocumentVerificationLinkResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`RequestDocumentVerificationLinkResponse!`](/api-reference/types/request-document-verification-link-response) — Response type for user verification request # requestGiftCardPurchase Source: https://docs.fluz.app/api-reference/mutations/request-gift-card-purchase Requests manager approval to purchase a gift card. Requests manager approval to purchase a gift card. ```graphql theme={null} mutation { requestGiftCardPurchase( input: RequestGiftCardPurchaseInput! ): RequestApprovalResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`RequestApprovalResponse!`](/api-reference/types/request-approval-response) — Response returned from request approval mutations. # requestInternalTransfer Source: https://docs.fluz.app/api-reference/mutations/request-internal-transfer Requests manager approval for an internal transfer. Requests manager approval for an internal transfer. ```graphql theme={null} mutation { requestInternalTransfer( input: RequestInternalTransferInput! ): RequestApprovalResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`RequestApprovalResponse!`](/api-reference/types/request-approval-response) — Response returned from request approval mutations. # requestReimbursement Source: https://docs.fluz.app/api-reference/mutations/request-reimbursement Requests manager approval for a reimbursement. Requests manager approval for a reimbursement. ```graphql theme={null} mutation { requestReimbursement( input: RequestReimbursementInput! ): RequestApprovalResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`RequestApprovalResponse!`](/api-reference/types/request-approval-response) — Response returned from request approval mutations. # requestVirtualCard Source: https://docs.fluz.app/api-reference/mutations/request-virtual-card Requests manager approval to create a virtual card. Requests manager approval to create a virtual card. ```graphql theme={null} mutation { requestVirtualCard( input: RequestVirtualCardInput! ): RequestApprovalResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`RequestApprovalResponse!`](/api-reference/types/request-approval-response) — Response returned from request approval mutations. # requestVirtualCardLimitChange Source: https://docs.fluz.app/api-reference/mutations/request-virtual-card-limit-change Requests manager approval to change a virtual card limit. Requests manager approval to change a virtual card limit. ```graphql theme={null} mutation { requestVirtualCardLimitChange( input: RequestVirtualCardLimitChangeInput! ): RequestApprovalResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`RequestApprovalResponse!`](/api-reference/types/request-approval-response) — Response returned from request approval mutations. # revealGiftCardByGiftCardId Source: https://docs.fluz.app/api-reference/mutations/reveal-gift-card-by-gift-card-id Reveals the Gift Card Code. Reveals the Gift Card Code. It has an irreversible effect on the Gift Card status. Requires REVEAL\_GIFTCARD scope. ```graphql theme={null} mutation { revealGiftCardByGiftCardId( giftCardId: UUID! ): GiftCardCode } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`GiftCardCode`](/api-reference/types/gift-card-code) — GiftCardCode contains the redemption credentials for a Merchant. # revealVirtualCardByVirtualCardId Source: https://docs.fluz.app/api-reference/mutations/reveal-virtual-card-by-virtual-card-id Reveals the full card number and CVV of a virtual card, along with its billing details. Reveals the full card number and CVV of a virtual card, along with its billing details. Requires PCI\_COMPLIANCE from the developer and REVEAL\_VIRTUALCARD scope. PERSONAL/Private applications are exempt from PCI\_COMPLIANCE requirement. ```graphql theme={null} mutation { revealVirtualCardByVirtualCardId( virtualCardId: UUID! ): VirtualCardDetails } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`VirtualCardDetails`](/api-reference/types/virtual-card-details) — The VirtualCardDetails type represents the details of a virtual card. # setBackupFundingSource Source: https://docs.fluz.app/api-reference/mutations/set-backup-funding-source setBackupFundingSource sets a bank card as the account's backup funding source, replacing any previous backup. setBackupFundingSource sets a bank card as the account's backup funding source, replacing any previous backup. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { setBackupFundingSource( input: SetBackupFundingSourceInput! ): DefaultFundingSource } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`DefaultFundingSource`](/api-reference/types/default-funding-source) — DefaultFundingSource represents an account's default funding source: the primary payment method (a bank account, charged first) and the backup payment method (a bank card, charged if the primary can't be charged). # setPrimaryFundingSource Source: https://docs.fluz.app/api-reference/mutations/set-primary-funding-source setPrimaryFundingSource sets a bank account as the account's primary funding source, replacing any previous primary. setPrimaryFundingSource sets a bank account as the account's primary funding source, replacing any previous primary. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { setPrimaryFundingSource( input: SetPrimaryFundingSourceInput! ): DefaultFundingSource } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`DefaultFundingSource`](/api-reference/types/default-funding-source) — DefaultFundingSource represents an account's default funding source: the primary payment method (a bank account, charged first) and the backup payment method (a bank card, charged if the primary can't be charged). # setVirtualCardPIN Source: https://docs.fluz.app/api-reference/mutations/set-virtual-card-pin Mutation to set the PIN on eligible virtual cards that have not yet had a PIN set. Mutation to set the PIN on eligible virtual cards that have not yet had a PIN set. The request adds the cards to a queue and the response indicates whether it was successfully enqueued. The actual PIN updating can take a few minutes to process. Requires CREATE\_VIRTUALCARD scope. ```graphql theme={null} mutation { setVirtualCardPIN( input: SetVirtualCardPINInput! ): SetVirtualCardPINResponse } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`SetVirtualCardPINResponse`](/api-reference/types/set-virtual-card-pinresponse) — Shows whether setting the PIN on eligible virtual cards was successful. # transferInternalBalance Source: https://docs.fluz.app/api-reference/mutations/transfer-internal-balance Mutation for transferring between user cash balances. Mutation for transferring between user cash balances. Requires MAKE\_INTERNAL\_TRANSFER scope. ```graphql theme={null} mutation { transferInternalBalance( input: TransferInternalBalanceInput! ): TransferInternalBalanceResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`TransferInternalBalanceResponse!`](/api-reference/types/transfer-internal-balance-response) — Response type for the transferInternalBalance mutation, returning the deposit, withdrawal records and updated balances. # transferObjectOwner Source: https://docs.fluz.app/api-reference/mutations/transfer-object-owner Transfer ownership of an existing object to another user on the same account. Transfer ownership of an existing object to another user on the same account. Requires MANAGE\_SUBUSERS. Forwards to auth-service PATCH /api/v1/object-owners/:objectOwnerId/owner. ```graphql theme={null} mutation { transferObjectOwner( objectOwnerId: UUID! userId: UUID! ): TransferObjectOwnerResponse } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`TransferObjectOwnerResponse`](/api-reference/types/transfer-object-owner-response) # unlockVirtualCard Source: https://docs.fluz.app/api-reference/mutations/unlock-virtual-card Mutation to unlock a previously locked virtual card. Mutation to unlock a previously locked virtual card. Requires EDIT\_VIRTUALCARD scope. ```graphql theme={null} mutation { unlockVirtualCard( input: UnlockVirtualCardInput! ): UnlockVirtualCardResponse } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`UnlockVirtualCardResponse`](/api-reference/types/unlock-virtual-card-response) # updateBankCardNickname Source: https://docs.fluz.app/api-reference/mutations/update-bank-card-nickname updateBankCardNickname updates the nickname of a bank card. updateBankCardNickname updates the nickname of a bank card. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { updateBankCardNickname( input: UpdateBankCardNicknameInput! ): BankCard } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`BankCard`](/api-reference/types/bank-card) — BankCard is a record of a bank card added by a user. # updateBankCardPreferredMerchantCategoryCode Source: https://docs.fluz.app/api-reference/mutations/update-bank-card-preferred-merchant-category-code updateBankCardPreferredMerchantCategoryCode updates the preferred merchant category code (MCC) for a bank card. updateBankCardPreferredMerchantCategoryCode updates the preferred merchant category code (MCC) for a bank card. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { updateBankCardPreferredMerchantCategoryCode( input: UpdateBankCardPreferredMerchantCategoryCodeInput! ): BankCard } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`BankCard`](/api-reference/types/bank-card) — BankCard is a record of a bank card added by a user. # updateTransactionMetadata Source: https://docs.fluz.app/api-reference/mutations/update-transaction-metadata Updates the memo, category, and/or attachment on an existing transaction. Updates the memo, category, and/or attachment on an existing transaction. Requires LIST\_PAYMENT and LIST\_PURCHASES scope. ```graphql theme={null} mutation { updateTransactionMetadata( input: UpdateTransactionMetadataInput! ): Transaction } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`Transaction`](/api-reference/types/transaction) — Transaction represents a comprehensive record of all account activity including purchases, deposits, withdrawals, transfers, and payouts. # updateUserCashBalance Source: https://docs.fluz.app/api-reference/mutations/update-user-cash-balance Mutation for updating a user cash balance account. Mutation for updating a user cash balance account. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} mutation { updateUserCashBalance( input: UpdateUserCashBalanceInput! ): UserCashBalance! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`UserCashBalance!`](/api-reference/types/user-cash-balance) — UserCashBalance represents a user's cash balance account. # verifyUserInformation Source: https://docs.fluz.app/api-reference/mutations/verify-user-information Verify and record a user's personal information. Verify and record a user's personal information. ```graphql theme={null} mutation { verifyUserInformation( firstName: String! lastName: String! streetLine1: String! streetLine2: String city: String! state: String! postalCode: String! country: String! dateOfBirth: String! ssnLast4: String! forceRetry: Boolean ): VerifyUserInformationResponse } ``` ## Arguments The Fluz user's first name. The Fluz user's last name. The street address line 1. The street address line 2. The address city. The address state. The postal code. The address country. The user's date of birth (format MM/DD/YYYY). The user's SSN last 4 digits. Whether to bypass duplicate-verification checks and force a new verification attempt. ## Returns [`VerifyUserInformationResponse`](/api-reference/types/verify-user-information-response) — Represents the response returned from the verifyUserInformation mutation. # verifyUserPrefillInformation Source: https://docs.fluz.app/api-reference/mutations/verify-user-prefill-information Verify and record a user's personal information using prefilled data already on file. Verify and record a user's personal information using prefilled data already on file. ```graphql theme={null} mutation { verifyUserPrefillInformation( forceRetry: Boolean ): VerifyUserInformationResponse } ``` ## Arguments Whether to bypass duplicate-verification checks and force a new verification attempt. ## Returns [`VerifyUserInformationResponse`](/api-reference/types/verify-user-information-response) — Represents the response returned from the verifyUserInformation mutation. # withdrawCashBalance Source: https://docs.fluz.app/api-reference/mutations/withdraw-cash-balance Mutation for withdrawing from a cash balance. Mutation for withdrawing from a cash balance. Supports ACH, PayPal, Venmo, and push-to-card methods. Use getWithdrawFeeEstimate to preview fees before submitting. Requires MAKE\_WITHDRAWAL scope. ```graphql theme={null} mutation { withdrawCashBalance( input: WithdrawCashBalanceInput! ): WithdrawCashBalanceResponse! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`WithdrawCashBalanceResponse!`](/api-reference/types/withdraw-cash-balance-response) — Response type for the withdrawCashBalance mutation, returning the withdrawal record and updated balances. # getMerchants Source: https://docs.fluz.app/api-reference/queries/get-merchants Get the active merchant catalog. Get the active merchant catalog. Requires LIST\_OFFERS scope. ```graphql theme={null} query { getMerchants( name: String paginate: OffsetInput offerTypes: OfferTypesInput filterBy: FilterByInput ): [Merchant]! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* Apply fine-grained filters to the offers. Example: \{ deliveryFormat: URL } will only return merchants that have gift card offers with the URL delivery format. ## Returns [`[Merchant]!`](/api-reference/types/merchant) — Merchant is place that a Gift Card can be use, or can charge a user's VirtualCard. # getOfferQuote Source: https://docs.fluz.app/api-reference/queries/get-offer-quote Get the best offer for merchant based on matching arguments. Get the best offer for merchant based on matching arguments. Requires LIST\_OFFERS scope. This requires Fluz to confirm the inventory, response time varies by vendors. ```graphql theme={null} query { getOfferQuote( input: GetOfferQuoteInput! ): Offer } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`Offer`](/api-reference/types/offer) — Offer is a discounted offer for a merchant. # getPlaidBankAccountBalance Source: https://docs.fluz.app/api-reference/queries/get-plaid-bank-account-balance Gets the latest stored safe Plaid balance for one owned bank account. Gets the latest stored safe Plaid balance for one owned bank account. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} query { getPlaidBankAccountBalance( input: PlaidBankAccountInput! ): PlaidBankBalance } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`PlaidBankBalance`](/api-reference/types/plaid-bank-balance) — Latest safe Plaid bank balance for a persisted bank account. # getPlaidBankAccountSpendPower Source: https://docs.fluz.app/api-reference/queries/get-plaid-bank-account-spend-power Gets the latest safe spend power for one owned Plaid bank account. Gets the latest safe spend power for one owned Plaid bank account. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} query { getPlaidBankAccountSpendPower( input: PlaidBankAccountInput! ): PlaidBankAccountSpendPower } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`PlaidBankAccountSpendPower`](/api-reference/types/plaid-bank-account-spend-power) — Safe spend power calculated for one Plaid-linked bank account. # getPlaidBankAccounts Source: https://docs.fluz.app/api-reference/queries/get-plaid-bank-accounts Lists safe Plaid bank accounts owned by the authenticated account. Lists safe Plaid bank accounts owned by the authenticated account. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} query { getPlaidBankAccounts( input: PlaidBankAccountFilterInput ): [PlaidBankAccount!]! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[PlaidBankAccount!]!`](/api-reference/types/plaid-bank-account) — Safe persisted Plaid bank account metadata. # getPlaidBankBalances Source: https://docs.fluz.app/api-reference/queries/get-plaid-bank-balances Lists latest stored safe Plaid balances owned by the authenticated account. Lists latest stored safe Plaid balances owned by the authenticated account. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} query { getPlaidBankBalances( input: PlaidBankBalanceFilterInput ): [PlaidBankBalance!]! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[PlaidBankBalance!]!`](/api-reference/types/plaid-bank-balance) — Latest safe Plaid bank balance for a persisted bank account. # getPlaidBankTransactions Source: https://docs.fluz.app/api-reference/queries/get-plaid-bank-transactions Lists historical bank transactions populated by identity-service from Plaid. Lists historical bank transactions populated by identity-service from Plaid. Requires MANAGE\_PAYMENT scope. ```graphql theme={null} query { getPlaidBankTransactions( input: PlaidBankTransactionFilterInput ): PlaidBankTransactionPage! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`PlaidBankTransactionPage!`](/api-reference/types/plaid-bank-transaction-page) # getReferralUrl Source: https://docs.fluz.app/api-reference/queries/get-referral-url Get the referral url for a merchant. Get the referral url for a merchant. ```graphql theme={null} query { getReferralUrl( merchant: MerchantInput ): String } ``` ## Arguments *No description provided in the schema yet.* ## Returns `String` — The `String` scalar type represents textual data, represented as UTF-8 character sequences. # getSpendAccountPaycheckDepositForm Source: https://docs.fluz.app/api-reference/queries/get-spend-account-paycheck-deposit-form Retrieves a PDF paycheck direct-deposit form for a virtual account number belonging to a spend account (cash balance) of the authenticated user's account. Retrieves a PDF paycheck direct-deposit form for a virtual account number belonging to a spend account (cash balance) of the authenticated user's account. Defaults to the spend account's primary virtual account number when virtualAccountNumberId is omitted. depositAmount is required when depositType is FIXED. depositPercentage (1-100) is required when depositType is PERCENTAGE. Requires LIST\_PAYMENT scope. ```graphql theme={null} query { getSpendAccountPaycheckDepositForm( userCashBalanceId: UUID! virtualAccountNumberId: UUID depositType: PaycheckDepositType! depositAmount: Float depositPercentage: Float ): SpendAccountPdfDocument! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`SpendAccountPdfDocument!`](/api-reference/types/spend-account-pdf-document) — A PDF artifact containing ACH routing and account instructions for funding a spend account's virtual account number, returned as a base64-encoded string for the client to decode and render or download. # getSpendAccountPaymentInstructions Source: https://docs.fluz.app/api-reference/queries/get-spend-account-payment-instructions Retrieves a PDF payment instructions artifact (ACH routing + account details) for funding a virtual account number belonging to a spend account (cash balance) of the authenticated user's account. Retrieves a PDF payment instructions artifact (ACH routing + account details) for funding a virtual account number belonging to a spend account (cash balance) of the authenticated user's account. Defaults to the spend account's primary virtual account number when virtualAccountNumberId is omitted. Requires LIST\_PAYMENT scope. ```graphql theme={null} query { getSpendAccountPaymentInstructions( userCashBalanceId: UUID! virtualAccountNumberId: UUID ): SpendAccountPdfDocument! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`SpendAccountPdfDocument!`](/api-reference/types/spend-account-pdf-document) — A PDF artifact containing ACH routing and account instructions for funding a spend account's virtual account number, returned as a base64-encoded string for the client to decode and render or download. # getSpendAccountStatusLetter Source: https://docs.fluz.app/api-reference/queries/get-spend-account-status-letter Retrieves a PDF account status letter for a virtual account number belonging to a spend account (cash balance) of the authenticated user's account. Retrieves a PDF account status letter for a virtual account number belonging to a spend account (cash balance) of the authenticated user's account. Defaults to the spend account's primary virtual account number when virtualAccountNumberId is omitted. Set displayBalance to include the current balance on the letter. Requires LIST\_PAYMENT scope. ```graphql theme={null} query { getSpendAccountStatusLetter( userCashBalanceId: UUID! virtualAccountNumberId: UUID displayBalance: Boolean ): SpendAccountPdfDocument! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`SpendAccountPdfDocument!`](/api-reference/types/spend-account-pdf-document) — A PDF artifact containing ACH routing and account instructions for funding a spend account's virtual account number, returned as a base64-encoded string for the client to decode and render or download. # getSpendAccountVirtualAccountNumbers Source: https://docs.fluz.app/api-reference/queries/get-spend-account-virtual-account-numbers Retrieves the active virtual account number(s) for a spend account (cash balance) belonging to the authenticated user's account. Retrieves the active virtual account number(s) for a spend account (cash balance) belonging to the authenticated user's account. A spend account may have multiple active virtual account numbers, one of which is marked primary. Requires LIST\_PAYMENT scope. ```graphql theme={null} query { getSpendAccountVirtualAccountNumbers( userCashBalanceId: UUID! ): [SpendAccountVirtualAccountNumber!]! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[SpendAccountVirtualAccountNumber!]!`](/api-reference/types/spend-account-virtual-account-number) — A virtual account number (ACH routing + account number) tied to a spend account. # getTransactions Source: https://docs.fluz.app/api-reference/queries/get-transactions Retrieves paginated transaction history for the authenticated user's account. Retrieves paginated transaction history for the authenticated user's account. Supports comprehensive filtering and pagination. Requires LIST\_PAYMENT and LIST\_PURCHASES scope. ```graphql theme={null} query { getTransactions( filter: TransactionFilterInput paginate: OffsetInput ): TransactionConnection! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`TransactionConnection!`](/api-reference/types/transaction-connection) — Paginated response for transactions query. # getUserAddresses Source: https://docs.fluz.app/api-reference/queries/get-user-addresses getUserAddresses returns the user's addresses. getUserAddresses returns the user's addresses. ```graphql theme={null} query { getUserAddresses( paginate: OffsetInput ): [UserAddress] } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[UserAddress]`](/api-reference/types/user-address) — UserAddress represents a User Address record. # getUserCashBalanceById Source: https://docs.fluz.app/api-reference/queries/get-user-cash-balance-by-id Retrieves a single cash balance by ID for the authenticated user's account. Retrieves a single cash balance by ID for the authenticated user's account. Requires LIST\_PAYMENT scope. ```graphql theme={null} query { getUserCashBalanceById( userCashBalanceId: UUID! ): UserCashBalanceDetail! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`UserCashBalanceDetail!`](/api-reference/types/user-cash-balance-detail) — UserCashBalanceDetail represents a user's cash balance account with additional details. # getUserCashBalances Source: https://docs.fluz.app/api-reference/queries/get-user-cash-balances Retrieves paginated list of cash balances for the authenticated user's account. Retrieves paginated list of cash balances for the authenticated user's account. Supports comprehensive filtering and pagination. Requires LIST\_PAYMENT scope. ```graphql theme={null} query { getUserCashBalances( filter: UserCashBalanceFilterInput paginate: OffsetInput ): UserCashBalanceConnection! } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`UserCashBalanceConnection!`](/api-reference/types/user-cash-balance-connection) — Paginated response for cash balances query. # getUserPurchases Source: https://docs.fluz.app/api-reference/queries/get-user-purchases Retrieves the user's purchase history. Retrieves the user's purchase history. Requires LIST\_PURCHASES scope. ```graphql theme={null} query { getUserPurchases( filter: UserPurchaseFilterInput paginate: OffsetInput ): [UserPurchase] } ``` ## Arguments *No description provided in the schema yet.* *No description provided in the schema yet.* ## Returns [`[UserPurchase]`](/api-reference/types/user-purchase) — UserPurchase represents a record of a purchase made by a user, detailing the payment methods and other relevant information. # getVCShareLinks Source: https://docs.fluz.app/api-reference/queries/get-vcshare-links Get list of generated share links. Get list of generated share links. Requires CREATE\_SHARE\_LINK scope. ```graphql theme={null} query { getVCShareLinks( input: GetVCShareLinksInput! ): [GeneratedShareLink] } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[GeneratedShareLink]`](/api-reference/types/generated-share-link) — GeneratedShareLink represents a single generated share link. # getVirtualCardBalance Source: https://docs.fluz.app/api-reference/queries/get-virtual-card-balance Get balance for multiple virtual cards. Get balance for multiple virtual cards. Requires PCI\_COMPLIANCE and REVEAL\_VIRTUALCARD scope. ```graphql theme={null} query { getVirtualCardBalance( input: GetVirtualCardBalanceInput! ): [VirtualCardBalance!]! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[VirtualCardBalance!]!`](/api-reference/types/virtual-card-balance) — VirtualCardBalance provides information about virtual card balance. # getVirtualCardBulkOrderStatus Source: https://docs.fluz.app/api-reference/queries/get-virtual-card-bulk-order-status Checks bulk virtual card order status. Checks bulk virtual card order status. Requires PCI\_COMPLIANCE and REVEAL\_VIRTUALCARD scope. ```graphql theme={null} query { getVirtualCardBulkOrderStatus( input: GetVirtualCardBulkOrderStatusInput! ): VirtualCardBulkOrder } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`VirtualCardBulkOrder`](/api-reference/types/virtual-card-bulk-order) — VirtualCardBulkOrder provides information about bulk creation order. # getVirtualCardOffers Source: https://docs.fluz.app/api-reference/queries/get-virtual-card-offers Get virtual card offers. Get virtual card offers. Requires CREATE\_VIRTUALCARD scope. ```graphql theme={null} query { getVirtualCardOffers( input: GetVirtualCardOffersInput ): [VirtualCardOffer] } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[VirtualCardOffer]`](/api-reference/types/virtual-card-offer) — Represents the Bank identification numbers (BINs). # getVirtualCardTransactions Source: https://docs.fluz.app/api-reference/queries/get-virtual-card-transactions Get transactions for multiple virtual cards with filters. Get transactions for multiple virtual cards with filters. Requires PCI\_COMPLIANCE and REVEAL\_VIRTUALCARD scope. ```graphql theme={null} query { getVirtualCardTransactions( input: GetVirtualCardTransactionsInput! ): [VirtualCardTransactions!]! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[VirtualCardTransactions!]!`](/api-reference/types/virtual-card-transactions) — Virtual card transactions, grouped by virtual card ID. # getWallet Source: https://docs.fluz.app/api-reference/queries/get-wallet getWallet returns the user's wallet balance and payment methods. getWallet returns the user's wallet balance and payment methods. ```graphql theme={null} query { getWallet: GetWalletResponse } ``` ## Returns [`GetWalletResponse`](/api-reference/types/get-wallet-response) — GetWalletResponse represents the response to the get wallet request. # getWithdrawFeeEstimate Source: https://docs.fluz.app/api-reference/queries/get-withdraw-fee-estimate Estimate the fees for a withdrawal before submitting. Estimate the fees for a withdrawal before submitting. Returns the fee, net amount, and settlement timing. Requires MAKE\_WITHDRAWAL scope. ```graphql theme={null} query { getWithdrawFeeEstimate( input: GetWithdrawFeeEstimateInput! ): WithdrawFeeEstimate! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`WithdrawFeeEstimate!`](/api-reference/types/withdraw-fee-estimate) — Estimated fees and net amount for a withdrawal. # lookupBusiness Source: https://docs.fluz.app/api-reference/queries/lookup-business Look up business recipients by company name. Look up business recipients by company name. Searches both business name and DBA name, returns all matches. Throws an error if no matches found. ```graphql theme={null} query { lookupBusiness( input: BusinessLookupInput! ): [BusinessLookupResult!]! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`[BusinessLookupResult!]!`](/api-reference/types/business-lookup-result) — Represents a business recipient. # lookupUser Source: https://docs.fluz.app/api-reference/queries/lookup-user Look up a user recipient by phone number or email. Look up a user recipient by phone number or email. Returns a single user or throws an error if not found. ```graphql theme={null} query { lookupUser( input: UserLookupInput! ): UserLookupResult! } ``` ## Arguments *No description provided in the schema yet.* ## Returns [`UserLookupResult!`](/api-reference/types/user-lookup-result) — Represents a user recipient. # Account Source: https://docs.fluz.app/api-reference/types/account Represents the account operable by the application. **Object** Represents the account operable by the application. ## Fields The id of the account. The type of account. The name of the business or first and last name of the user. The seats owned with the account. The user associated with the account. The business associated with the account. # AddAuthorizedUserResponse Source: https://docs.fluz.app/api-reference/types/add-authorized-user-response Response returned from the addAuthorizedUser mutation. **Object** Response returned from the addAuthorizedUser mutation. ## Fields Whether the operation succeeded. The authorized user ID (UAC role assignment ID) created for the user on the account. Use this ID when calling removeAuthorizedUser. The roles assigned to the user. The status of the role assignment. The pending action ID for the invite, if created. Error details when the operation fails. # ApplicationUser Source: https://docs.fluz.app/api-reference/types/application-user Represents the User who grants access to the application with scopes. **Object** Represents the User who grants access to the application with scopes. ## Fields The userId that has granted the scopes. The user scopes available the application. The accounts that the user has access to. # ApprovalRequest Source: https://docs.fluz.app/api-reference/types/approval-request An open approval request for the caller's account. **Object** An open approval request for the caller's account. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ApprovalRequestActionError Source: https://docs.fluz.app/api-reference/types/approval-request-action-error Error details returned when an approval action cannot be submitted. **Object** Error details returned when an approval action cannot be submitted. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # ApprovalRequestActionResponse Source: https://docs.fluz.app/api-reference/types/approval-request-action-response Response returned from approve/decline approval request mutations. **Object** Response returned from approve/decline approval request mutations. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ApprovalRequestApprover Source: https://docs.fluz.app/api-reference/types/approval-request-approver An approver assigned to an approval request. **Object** An approver assigned to an approval request. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # AssignObjectOwnerResponse Source: https://docs.fluz.app/api-reference/types/assign-object-owner-response **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # AuthorizedUser Source: https://docs.fluz.app/api-reference/types/authorized-user Represents an authorized user on an account. **Object** Represents an authorized user on an account. ## Fields The authorized user ID (UAC role assignment ID). The roles assigned to the user on this account. The status of the role assignment. The email address of the authorized user. The phone number of the authorized user. The first name of the authorized user. The last name of the authorized user. *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # AuthorizedUserError Source: https://docs.fluz.app/api-reference/types/authorized-user-error Error details for authorized user mutation failures. **Object** Error details for authorized user mutation failures. ## Fields Brief error message describing the failure. Error code identifying the specific error type. # BankAccount Source: https://docs.fluz.app/api-reference/types/bank-account BankAccount is a record of a bank account added by a user. **Object** BankAccount is a record of a bank account added by a user. ## Fields The ID of the bank account. *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # BankCard Source: https://docs.fluz.app/api-reference/types/bank-card BankCard is a record of a bank card added by a user. **Object** BankCard is a record of a bank card added by a user. ## Fields The ID of the bank card. *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # BulkBalances Source: https://docs.fluz.app/api-reference/types/bulk-balances Balances across your connected users, one result per target. **Object** Balances across your connected users, one result per target. ## Fields *No description provided in the schema yet.* The number of targets addressed by the request. *No description provided in the schema yet.* *No description provided in the schema yet.* # BulkBalancesTargetResult Source: https://docs.fluz.app/api-reference/types/bulk-balances-target-result The result of a bulk balances request for one target user. **Object** The result of a bulk balances request for one target user. ## Fields Your reference id for the target user, when available. The accountId of the target user, when the target was resolved. Whether balances could be returned for this target. Failure detail when success is false. The target user's cash balances. Restricted to the permitted accounts when the user's grant is scoped to specific spend accounts. # BulkConnectedOAuthUser Source: https://docs.fluz.app/api-reference/types/bulk-connected-oauth-user A user connected to your application through an active OAuth grant. **Object** A user connected to your application through an active OAuth grant. ## Fields Your reference id for this user, if one was provided when the user connected. Users without an externalReferenceId cannot be selected individually by bulk operations and are only reachable in ALL\_CONNECTED mode. The accountId of the connected user. The userId of the connected user. The scopes the user has granted to your application. When the user connected to your application. # BulkConnectedOAuthUsers Source: https://docs.fluz.app/api-reference/types/bulk-connected-oauth-users A page of users connected to your application. **Object** A page of users connected to your application. ## Fields *No description provided in the schema yet.* The total number of connected users across all pages. Whether more connected users exist beyond this page. # BulkTargetError Source: https://docs.fluz.app/api-reference/types/bulk-target-error The per-target failure detail for a bulk operation. **Object** The per-target failure detail for a bulk operation. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # BulkTransactions Source: https://docs.fluz.app/api-reference/types/bulk-transactions Recent transactions across your connected users, one result per target. **Object** Recent transactions across your connected users, one result per target. ## Fields *No description provided in the schema yet.* The number of targets addressed by the request. *No description provided in the schema yet.* *No description provided in the schema yet.* # BulkTransactionsTargetResult Source: https://docs.fluz.app/api-reference/types/bulk-transactions-target-result The result of a bulk transactions request for one target user. **Object** The result of a bulk transactions request for one target user. ## Fields Your reference id for the target user, when available. The accountId of the target user, when the target was resolved. Whether transactions could be returned for this target. Failure detail when success is false. The target user's most recent transactions within the requested window, newest first — at most 20 per target. Restricted to the permitted accounts when the user's grant is scoped to specific spend accounts. The total number of transactions matching the window for this target. Whether the target has more transactions than the 20 returned. Use the asynchronous transactions export to retrieve complete history. # BulkUpdateErpTransactionMetadataResult Source: https://docs.fluz.app/api-reference/types/bulk-update-erp-transaction-metadata-result **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # Business Source: https://docs.fluz.app/api-reference/types/business Represents the Business who has access to the account. **Object** Represents the Business who has access to the account. ## Fields The businessId associated with the account. The name of the business. # BusinessCategory Source: https://docs.fluz.app/api-reference/types/business-category BusinessCategory represents a business category with its associated sub-categories. **Object** BusinessCategory represents a business category with its associated sub-categories. ## Fields Unique identifier for the business category. Name of the category. Description of the category. List of sub-categories associated with this category. # BusinessLookupResult Source: https://docs.fluz.app/api-reference/types/business-lookup-result Represents a business recipient. **Object** Represents a business recipient. ## Fields The account ID of the recipient. Formatted company name with DBA in parentheses. Format: "Business Name (DBA Name)" or just "Business Name" if no DBA. State of the business. Derived from the business's legal address. # BusinessSubCategory Source: https://docs.fluz.app/api-reference/types/business-sub-category BusinessSubCategory represents a sub-category within a business category. **Object** BusinessSubCategory represents a sub-category within a business category. ## Fields Unique identifier for the business sub-category. Name of the sub-category. # CardProvisioningUrl Source: https://docs.fluz.app/api-reference/types/card-provisioning-url **Object** *No description provided in the schema yet.* ## Fields Short URL that, when opened on the end-user's mobile device, launches the Fluz App Clip (iOS) or Fluz app (Android) and initiates the wallet-provisioning flow for the targeted virtual card. Treat it as a bearer secret: it stays redeemable for a short grace window after first use. Surfaceable as a QR code, SMS / email link, or in-app button. ISO-8601 timestamp at which the URL stops working (approximately 5 minutes after creation). Plan your delivery timing accordingly. # CashBalance Source: https://docs.fluz.app/api-reference/types/cash-balance CashBalance represents the balance of a user's cash. **Object** CashBalance represents the balance of a user's cash. ## Fields The available balance of the user's cash. The total balance of the user's cash. The pending balance of the user's cash. The lifetime balance of the user's cash. # CashBalanceDeposit Source: https://docs.fluz.app/api-reference/types/cash-balance-deposit CashBalanceDeposit represents a cash balance deposit, including details like amount, fee, and status. **Object** CashBalanceDeposit represents a cash balance deposit, including details like amount, fee, and status. ## Fields Unique identifier for the cash balance deposit. Display identifier for the deposit, used for user-facing purposes. The amount of the deposit. Fee associated with the deposit, if applicable. Identifier of the bank card used for the deposit. Identifier of the bank account used for the deposit. Identifier of the PayPal vault used for the deposit. Date and time when the transaction was made. Date and time when the deposit cleared, if applicable. Current status of the deposit. Expected date and time for the deposit to clear. Type of the cash balance deposit. Associated settlements for the deposit. Memo for the deposit. Category name for the deposit. URL of the attachment associated with this deposit. # CashBalanceSettlement Source: https://docs.fluz.app/api-reference/types/cash-balance-settlement CashBalanceSettlement representing a settlement record of a cash balance. **Object** CashBalanceSettlement representing a settlement record of a cash balance. ## Fields Unique identifier for the cash balance settlement. Identifier of the associated cash balance deposit. Type of availability for the settlement (instant or standard). Current status of the cash balance settlement. # CloDetails Source: https://docs.fluz.app/api-reference/types/clo-details [CLO Only] Contains specific details about rates and periods for a Card Linked Offer. **Object** \[CLO Only] Contains specific details about rates and periods for a Card Linked Offer. This object is null for non-CLO offer types. ## Fields The type of rate currently active (REGULAR or PROMO) based on evaluation of all periods against the current time. The standard reward rate (%) for the offer. The promotional reward rate (%) potentially active during a PROMO period. The reward rate (%) applied during a PROMO period *after* the promoMaxCap is exceeded. The maximum purchase amount up to which the promoRate applies during a PROMO period. If true, the promoBaseRate applies to the purchase amount exceeding the promoMaxCap during a PROMO period. The minimum purchase amount required to qualify for the CLO reward, if any. The start date (YYYY-MM-DD) of the *currently active* offer period, if any. The end date (YYYY-MM-DD) of the *currently active* offer period, if any. List of all defined periods for this offer's rates (past, present, future). # CloPeriod Source: https://docs.fluz.app/api-reference/types/clo-period [CLO Only] Represents a specific time period during which a Card Linked Offer rate applies. **Object** \[CLO Only] Represents a specific time period during which a Card Linked Offer rate applies. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # CloseUserCashBalanceResponse Source: https://docs.fluz.app/api-reference/types/close-user-cash-balance-response Response type for the closeUserCashBalance mutation, returning a closed user cash balance and affected virtual card records **Object** Response type for the closeUserCashBalance mutation, returning a closed user cash balance and affected virtual card records ## Fields *No description provided in the schema yet.* Virtual cards affected by the user cash balance closure. # CloseUserCashBalanceResponseVirtualCard Source: https://docs.fluz.app/api-reference/types/close-user-cash-balance-response-virtual-card Response type for a virtual card affected by user cash balance closure. **Object** Response type for a virtual card affected by user cash balance closure. ## Fields The ID associated with the virtual card. The last 4 digits of the virtual card number. # ClosedUserCashBalance Source: https://docs.fluz.app/api-reference/types/closed-user-cash-balance Response type for closing a user cash balance account. **Object** Response type for closing a user cash balance account. ## Fields Unique identifier for the user cash balance. Lifetime cash balance (cumulative total ever deposited). Custom nickname for the balance. Status of the cash balance account. Date and time when the account was created. Date and time when the account was closed. # CreatePlaidLinkAddressResponse Source: https://docs.fluz.app/api-reference/types/create-plaid-link-address-response **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* # CreateTransferResponse Source: https://docs.fluz.app/api-reference/types/create-transfer-response Represents the response returned from the createTransfer mutation. **Object** Represents the response returned from the createTransfer mutation. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # DeclinedTransaction Source: https://docs.fluz.app/api-reference/types/declined-transaction Transaction represents a comprehensive record of all account activity including purchases, deposits, withdrawals, transfers, and payouts. **Object** Transaction represents a comprehensive record of all account activity including purchases, deposits, withdrawals, transfers, and payouts. ## Fields Unique identifier of the declined transaction. Type of transaction that was declined, e.g. "Gift Card Purchase", "Virtual Card Purchase", "Add Money", "Withdrawal", "Transfer - Out", "Bill Payment Initiation", "Issue Virtual Card". Total transaction amount that was attempted, in the transaction's currency. Portion of the attempted amount that would have been funded from Fluz internal balances (cash/rewards/reserve/prepayment activity). Defaults to 0 when not applicable. Portion of the attempted amount that would have been funded from an external funding source (e.g. linked bank/card). Defaults to 0 when not applicable. ISO 4217 currency code for the transaction amount. Defaults to "USD" when not specified on the record. Type of the originating source record that produced this declined transaction (e.g. the originating domain/entity type). Identifier of the originating source record that produced this declined transaction. Identifier of the Fluz account that owns the transaction. Identifier of the user who attempted the transaction. May be absent for account-level/system transactions. Source of funds / origin label for the transaction (e.g. funding source or originating party). Destination of the transaction (e.g. merchant or recipient label). For merchant transactions this is typically the merchant. Outcome status of the transaction. One of DeclinedTransactionStatus: "DECLINED" or "FAILED". Identifier of the merchant associated with the transaction, when known. Identifier of the resolved merchant descriptor used to enrich merchant metadata. Identifier of the liability record associated with the transaction, when applicable. Display name of the merchant. Populated from the transaction destination when a merchant is identified; otherwise null. Country of the merchant. City of the merchant. State/region of the merchant. URL of the logo to display for the transaction (typically the merchant/brand logo). Category/classification of the transaction or merchant. Last four digits of the card used for the transaction. Human-friendly display name of the card used for the transaction. Identifier of the virtual card involved in the transaction, when applicable. Virtual card program/issuer label for the virtual card, when applicable. Channel through which the transaction was initiated (e.g. app, web, integration). Type of the external funding source used (e.g. bank account, debit card). Identifier of the external funding source used. Subtype of the funding source (e.g. checking vs. savings, card network subtype). True if the user's cash balance was applied/attempted for this transaction. True if the gift-card prepayment balance was applied/attempted for this transaction. True if the rewards balance was applied/attempted for this transaction. True if the reserve balance was applied/attempted for this transaction. Timestamp of the transaction, as an ISO 8601 datetime String (derived from the record's creation time). Nickname of the spend account used. Falls back to "Main account" when no specific nickname is set. Short, user-facing title summarizing the decline (from the resolved decline display content). User-facing reason explaining why the transaction was declined. Longer user-facing description providing additional detail about the decline. Call-to-action text suggesting the next step the user can take in response to the decline. Categorization of the decline (used to group/route decline messaging). Approval action configured for the resolved decline code, when one is mapped. Identifier of the offer associated with the transaction, when applicable. Card issuer associated with the card/transaction. Bank Identification Number (first digits) of the card used. Spend-limit amount relevant to the decline (e.g. the limit that was exceeded), as a String. Duration/window the spend limit applies to (e.g. per-transaction, daily, monthly). Indicates whether the card is set to lock on next use. Date associated with a card lock, when applicable. Nickname of the bank account associated with the funding source, when derivable. Last four digits of the bank account associated with the funding source. Privacy flag indicating whether the transaction should be treated as private. Timestamp when the transaction was created. Timestamp when the transaction was last updated. # DeclinedTransactionConnection Source: https://docs.fluz.app/api-reference/types/declined-transaction-connection Paginated response for getDeclinedTransactions query. **Object** Paginated response for getDeclinedTransactions query. ## Fields List of transactions. Total count of transactions matching the filter. Whether there are more results available. # DefaultFundingSource Source: https://docs.fluz.app/api-reference/types/default-funding-source DefaultFundingSource represents an account's default funding source: the primary payment method (a bank account, charged first) and the backup payment method (a bank card, charged if the primary can't be charged). **Object** DefaultFundingSource represents an account's default funding source: the primary payment method (a bank account, charged first) and the backup payment method (a bank card, charged if the primary can't be charged). ## Fields The primary funding source (bank account) charged first for transactions. Null if no primary is set (or the stored primary is not a bank account). The backup funding source (bank card) charged if the primary can't be charged. Null if no backup is set (or the stored backup is not a bank card). # DepositCashBalanceResponse Source: https://docs.fluz.app/api-reference/types/deposit-cash-balance-response Response type for the depositCashBalance mutation, returning the resulting deposits and current user balances. **Object** Response type for the depositCashBalance mutation, returning the resulting deposits and current user balances. ## Fields List of cash balance deposits made as part of the mutation. User's current balances. # ErpBulkFailure Source: https://docs.fluz.app/api-reference/types/erp-bulk-failure **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # ErpError Source: https://docs.fluz.app/api-reference/types/erp-error A single per-item failure in bulkUpdateErpTransactionMetadata. **Object** A single per-item failure in bulkUpdateErpTransactionMetadata. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # ErpReferenceItem Source: https://docs.fluz.app/api-reference/types/erp-reference-item A chart-of-accounts entry, vendor, or customer imported from the connected accounting provider (e.g. **Object** A chart-of-accounts entry, vendor, or customer imported from the connected accounting provider (e.g. QuickBooks). ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* Only populated for chart-of-accounts entries. Only populated for chart-of-accounts entries. *No description provided in the schema yet.* *No description provided in the schema yet.* # ErpReferenceItemConnection Source: https://docs.fluz.app/api-reference/types/erp-reference-item-connection **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ErpReferenceItemRef Source: https://docs.fluz.app/api-reference/types/erp-reference-item-ref A lightweight reference to a category, vendor, or customer attached to a transaction's ERP metadata. **Object** A lightweight reference to a category, vendor, or customer attached to a transaction's ERP metadata. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # ErpTransactionMetadata Source: https://docs.fluz.app/api-reference/types/erp-transaction-metadata The current ERP-side categorization state of a transaction. **Object** The current ERP-side categorization state of a transaction. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ErpTransactionMetadataConnection Source: https://docs.fluz.app/api-reference/types/erp-transaction-metadata-connection **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ErrorInfo Source: https://docs.fluz.app/api-reference/types/error-info Error information. **Object** Error information. ## Fields Error message. Error code. # GenerateUserAccessTokenResponse Source: https://docs.fluz.app/api-reference/types/generate-user-access-token-response Represents the response returned from the generateUserAccessToken mutation. **Object** Represents the response returned from the generateUserAccessToken mutation. Includes the access token and the scopes that the token grants. ## Fields The User Access JWT for API requests. The User Access refresh token for API requests. The list of scopes granted to the token. # GeneratedShareLink Source: https://docs.fluz.app/api-reference/types/generated-share-link GeneratedShareLink represents a single generated share link. **Object** GeneratedShareLink represents a single generated share link. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # GetWalletResponse Source: https://docs.fluz.app/api-reference/types/get-wallet-response GetWalletResponse represents the response to the get wallet request. **Object** GetWalletResponse represents the response to the get wallet request. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # GiftCard Source: https://docs.fluz.app/api-reference/types/gift-card GiftCard represents a record of a gift card purchased by a user. **Object** GiftCard represents a record of a gift card purchased by a user. ## Fields The ID associated with the gift card. The ID of the purchase that created this gift card. The display ID of the purchase that created this gift card. The ID of the user who made the purchase. The expiration date of the gift card. The status of the Gift Card. The value the gift card was purchased at (its denomination / face value). The remaining balance on the gift card. The ISO currency code of the gift card's value. The time the gift card was created. A connection to the merchant that the gift card can be redeemed at. The terms and conditions for this gift card's offer. The delivery format of the offer this gift card was purchased from. The active offer on a merchant may change over time, so the delivery format of the merchant's current offer is not necessarily the same as the delivery format of the offer used to purchase this gift card. # GiftCardCode Source: https://docs.fluz.app/api-reference/types/gift-card-code GiftCardCode contains the redemption credentials for a Merchant. **Object** GiftCardCode contains the redemption credentials for a Merchant. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # LockVirtualCardResponse Source: https://docs.fluz.app/api-reference/types/lock-virtual-card-response **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # Merchant Source: https://docs.fluz.app/api-reference/types/merchant Merchant is place that a Gift Card can be use, or can charge a user's VirtualCard. **Object** Merchant is place that a Gift Card can be use, or can charge a user's VirtualCard. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* A short marketing description of the merchant (e.g. product details, category summary). # MerchantCategoryCode Source: https://docs.fluz.app/api-reference/types/merchant-category-code MerchantCategoryCode represents the classifier for a business by the types of goods or services it provides. **Object** MerchantCategoryCode represents the classifier for a business by the types of goods or services it provides. ## Fields Four digit merchant category code. Display description. Mcc description. # ObjectOwner Source: https://docs.fluz.app/api-reference/types/object-owner Object ownership metadata returned from assign/transfer mutations. **Object** Object ownership metadata returned from assign/transfer mutations. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ObjectOwnerError Source: https://docs.fluz.app/api-reference/types/object-owner-error **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # Offer Source: https://docs.fluz.app/api-reference/types/offer Offer is a discounted offer for a merchant. **Object** Offer is a discounted offer for a merchant. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* StockInfo is an object where the key:value pairs represent the amount in stock for each denomination (key = denomination; value = amount in stock). It's only available if the offer `hasStockInfo`. This requires Fluz to confirm the inventory, response time varies by vendors. \[CLO Only] Contains specific details about rates and periods for a Card Linked Offer. Returns null for non-CLO offer types. The terms and conditions text for this offer (e.g. legal language, restrictions, expiration policies). # OfferRate Source: https://docs.fluz.app/api-reference/types/offer-rate OfferRate is the rate of a specific offer. **Object** OfferRate is the rate of a specific offer. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # Paypal Source: https://docs.fluz.app/api-reference/types/paypal Paypal represents a PayPal account added by a user. **Object** Paypal represents a PayPal account added by a user. ## Fields The ID of the PayPal account. *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidBankAccount Source: https://docs.fluz.app/api-reference/types/plaid-bank-account Safe persisted Plaid bank account metadata. **Object** Safe persisted Plaid bank account metadata. Plaid access tokens, account numbers, and routing numbers are never returned. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidBankAccountBalanceRefreshResponse Source: https://docs.fluz.app/api-reference/types/plaid-bank-account-balance-refresh-response Result of a rate-limited realtime refresh for one Plaid bank account's owning institution. **Object** Result of a rate-limited realtime refresh for one Plaid bank account's owning institution. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidBankAccountSpendPower Source: https://docs.fluz.app/api-reference/types/plaid-bank-account-spend-power Safe spend power calculated for one Plaid-linked bank account. **Object** Safe spend power calculated for one Plaid-linked bank account. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidBankBalance Source: https://docs.fluz.app/api-reference/types/plaid-bank-balance Latest safe Plaid bank balance for a persisted bank account. **Object** Latest safe Plaid bank balance for a persisted bank account. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidBankConnectionRefreshResponse Source: https://docs.fluz.app/api-reference/types/plaid-bank-connection-refresh-response Result of a cached refresh for all Plaid connections owned by the authenticated account. **Object** Result of a cached refresh for all Plaid connections owned by the authenticated account. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidBankTransaction Source: https://docs.fluz.app/api-reference/types/plaid-bank-transaction Safe persisted bank transaction populated by identity-service from Plaid. **Object** Safe persisted bank transaction populated by identity-service from Plaid. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidBankTransactionPage Source: https://docs.fluz.app/api-reference/types/plaid-bank-transaction-page **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidLinkCompletionResponse Source: https://docs.fluz.app/api-reference/types/plaid-link-completion-response Safe public result of completing a Plaid Link flow. **Object** Safe public result of completing a Plaid Link flow. Store platformItemId for future relinks. Access tokens, public tokens, and identity-match details are never returned. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidLinkTokenResponse Source: https://docs.fluz.app/api-reference/types/plaid-link-token-response Response containing the token required to initialize Plaid Link. **Object** Response containing the token required to initialize Plaid Link. Use linkToken immediately; do not store it. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidLinkedBankAccount Source: https://docs.fluz.app/api-reference/types/plaid-linked-bank-account Linked bank account returned after identity-service completes Plaid ingestion. **Object** Linked bank account returned after identity-service completes Plaid ingestion. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidVerifyMember Source: https://docs.fluz.app/api-reference/types/plaid-verify-member Plaid member that requires relinking after a refresh attempt. **Object** Plaid member that requires relinking after a refresh attempt. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ProgramLimits Source: https://docs.fluz.app/api-reference/types/program-limits Represents the bank program spent limits. **Object** Represents the bank program spent limits. ## Fields The bank program daily spend limit. The bank program weekly spend limit. The bank program monthly spend limit. # RefreshUserAccessTokenResponse Source: https://docs.fluz.app/api-reference/types/refresh-user-access-token-response Represents the response returned from the generateUserAccessToken mutation. **Object** Represents the response returned from the generateUserAccessToken mutation. Includes the access token and the scopes that the token grants. ## Fields The User Access JWT for API requests. The list of scopes granted to the token. # RegisterBusinessResponse Source: https://docs.fluz.app/api-reference/types/register-business-response Response from business registration. **Object** Response from business registration. On success: returns accountId and kybStatus. On error: returns success: false and error information. ## Fields The account ID of the newly created business (present on success). The KYB status of the business (present on success). Indicates if the registration was successful (present on error). Error information if registration failed (present on error). # RegisterUserError Source: https://docs.fluz.app/api-reference/types/register-user-error Error details for registerUser mutation failures. **Object** Error details for registerUser mutation failures. ## Fields Brief error message describing the failure. Error code identifying the specific error type. # RegisterUserResponse Source: https://docs.fluz.app/api-reference/types/register-user-response Represents the response returned from the registerUser mutation. **Object** Represents the response returned from the registerUser mutation. Includes the success flag. ## Fields Flag representing the success of calling registerUser mutation. *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* Error object containing message and error code when registration fails. # RemoveAuthorizedUserResponse Source: https://docs.fluz.app/api-reference/types/remove-authorized-user-response Response returned from the removeAuthorizedUser mutation. **Object** Response returned from the removeAuthorizedUser mutation. ## Fields Whether the operation succeeded. The authorized user ID (UAC role assignment ID) that was deactivated. The updated status of the role assignment. Error details when the operation fails. # RemovePlaidBankInstitutionResponse Source: https://docs.fluz.app/api-reference/types/remove-plaid-bank-institution-response **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # RequestApprovalError Source: https://docs.fluz.app/api-reference/types/request-approval-error Error details returned when an approval request cannot be submitted. **Object** Error details returned when an approval request cannot be submitted. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # RequestApprovalResponse Source: https://docs.fluz.app/api-reference/types/request-approval-response Response returned from request approval mutations. **Object** Response returned from request approval mutations. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # RequestDocumentVerificationLinkResponse Source: https://docs.fluz.app/api-reference/types/request-document-verification-link-response Response type for user verification request **Object** Response type for user verification request ## Fields The user ID being verified The type of verification being performed The unique identifier for this verification request The URL where the verification can be performed The verification status. The response message if any. # RewardsBalance Source: https://docs.fluz.app/api-reference/types/rewards-balance RewardsBalance represents the balance of a user's rewards. **Object** RewardsBalance represents the balance of a user's rewards. ## Fields The available balance of the user's rewards. The total balance of the user's rewards. The lifetime balance of the user's rewards. # Seat Source: https://docs.fluz.app/api-reference/types/seat Represents the network position held by the entity. **Object** Represents the network position held by the entity. ## Fields The seat id associated with the account. The string value of seat position. # SetVirtualCardPINResponse Source: https://docs.fluz.app/api-reference/types/set-virtual-card-pinresponse Shows whether setting the PIN on eligible virtual cards was successful. **Object** Shows whether setting the PIN on eligible virtual cards was successful. ## Fields Returns whether the operation was successfully queued. Returns if an error occurred while trying to validate your PIN. # ShareLinkRecipientRegistrationResult Source: https://docs.fluz.app/api-reference/types/share-link-recipient-registration-result Per-recipient outcome for REGISTER_USER share link generation. **Object** Per-recipient outcome for REGISTER\_USER share link generation. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ShareRequest Source: https://docs.fluz.app/api-reference/types/share-request ShareRequest represents a response from creating a share link. **Object** ShareRequest represents a response from creating a share link. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # ShareRequestDetails Source: https://docs.fluz.app/api-reference/types/share-request-details ShareRequestDetails represents the configuration settings of the share request. **Object** ShareRequestDetails represents the configuration settings of the share request. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # SpendAccountPdfDocument Source: https://docs.fluz.app/api-reference/types/spend-account-pdf-document A PDF artifact containing ACH routing and account instructions for funding a spend account's virtual account number, returned as a base64-encoded string for the client to decode and render or download. **Object** A PDF artifact containing ACH routing and account instructions for funding a spend account's virtual account number, returned as a base64-encoded string for the client to decode and render or download. ## Fields Base64-encoded contents of the payment instructions PDF. Suggested file name for the PDF, e.g. when saving or downloading it on the client. # SpendAccountVirtualAccountNumber Source: https://docs.fluz.app/api-reference/types/spend-account-virtual-account-number A virtual account number (ACH routing + account number) tied to a spend account. **Object** A virtual account number (ACH routing + account number) tied to a spend account. A spend account may have multiple active virtual account numbers, one of which is marked primary. ## Fields Unique identifier for the virtual account number. Identifier of the spend account (cash balance) this virtual account number belongs to. The ACH routing number for the virtual account number. The full account number for the virtual account number. Every reveal of the full number is audit-logged. The last four digits of the account number. Optional custom nickname for the virtual account number. Status of the virtual account number. Whether this is the primary virtual account number for the spend account. Which debits are permitted against this virtual account number. Date and time when the virtual account number was created. # StockInfoFixedType Source: https://docs.fluz.app/api-reference/types/stock-info-fixed-type StockInfoFixedType describes the available denominations for fixed offers, and the amount in stock for each denomination. **Object** StockInfoFixedType describes the available denominations for fixed offers, and the amount in stock for each denomination. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # StockInfoVariableType Source: https://docs.fluz.app/api-reference/types/stock-info-variable-type StockInfoVariableType describes the available denomination range for variable offers. **Object** StockInfoVariableType describes the available denomination range for variable offers. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # SupervisionAssignment Source: https://docs.fluz.app/api-reference/types/supervision-assignment **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # SupervisionAssignmentError Source: https://docs.fluz.app/api-reference/types/supervision-assignment-error **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # SupervisionAssignmentResponse Source: https://docs.fluz.app/api-reference/types/supervision-assignment-response **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # Transaction Source: https://docs.fluz.app/api-reference/types/transaction Transaction represents a comprehensive record of all account activity including purchases, deposits, withdrawals, transfers, and payouts. **Object** Transaction represents a comprehensive record of all account activity including purchases, deposits, withdrawals, transfers, and payouts. ## Fields Unique identifier for the transaction record. The display name of the user associated with this transaction. The ID of the account that owns this transaction. The ID of the user that initiated this transaction. The type of transaction (e.g., GIFT\_CARD\_PURCHASE, DEPOSIT, WITHDRAWAL, TRANSFER). The primary transaction amount. The destination of the transaction (merchant name, recipient, etc.). The source of the transaction (funding source, sender, etc.). Activity from external funding sources (bank cards, bank accounts). Activity from Fluz internal balances (cash, rewards, prepayment). Transaction fee amount. Cashback earned from this transaction. Available balance in gift card prepayment after this transaction. Total balance in gift card prepayment after this transaction. Available cash balance after this transaction. Total cash balance after this transaction. Available rewards (seat) balance after this transaction. Total rewards (seat) balance after this transaction. Available reserve balance after this transaction. Total reserve balance after this transaction. Available balance in other cash accounts after this transaction. Total balance in other cash accounts after this transaction. Current status of the transaction. External reference ID (e.g., purchase display ID, deposit ID). Human-readable description of the transaction. Additional notes about the transaction. Transaction category (e.g., Shopping, Travel, Bills). Last four digits of the card used (if applicable). Display name of the card used (if applicable). Original amount in foreign currency (for international transactions). Currency code of the original amount (e.g., EUR, GBP). Currency conversion rate applied. ID of the merchant associated with this transaction. ID of the transaction descriptor. Virtual card program used (e.g., LITHIC, MARQETA, HIGHNOTE\_CFSB). Cashback rate percentage applied. Bonus cashback rate percentage applied. Channel through which the transaction was initiated. Type of the funding source used. Logo URL for the merchant or source. Logo URL for the banking institution (for bank-funded transactions). Logo URL for challenges/milestones (for bonus transactions). Account ID of invited user (for referral-related transactions). Expected date when pending transaction will be cleared. ID of associated liability (for bill payments). Whether this transaction affected the gift card prepayment balance. Whether this transaction affected the cash balance. Whether this transaction affected the rewards (seat) balance. Whether this transaction affected the reserve balance. ID of the transfer record (for P2P transfers). ID of the application transaction was triggered from Name of the application transaction was triggered from ID of the specific user cash balance used for this transaction. IDs of related withdrawals Timestamp when the transaction was created. Timestamp when the transaction was last updated. User-defined memo attached to this transaction. User-defined category attached to this transaction. URL of the attachment associated with this transaction. The current ERP-side categorization state of this transaction. Null when the account has no active ERP connection or when this transaction has no ERP metadata yet. # TransactionConnection Source: https://docs.fluz.app/api-reference/types/transaction-connection Paginated response for transactions query. **Object** Paginated response for transactions query. ## Fields List of transactions. Total count of transactions matching the filter. Whether there are more results available. # TransferInternalBalanceResponse Source: https://docs.fluz.app/api-reference/types/transfer-internal-balance-response Response type for the transferInternalBalance mutation, returning the deposit, withdrawal records and updated balances. **Object** Response type for the transferInternalBalance mutation, returning the deposit, withdrawal records and updated balances. ## Fields Cash balance deposit made as part of the mutation. Withdrawal transaction record created. User's current balances after the transfer. # TransferObjectOwnerResponse Source: https://docs.fluz.app/api-reference/types/transfer-object-owner-response **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # UnlockVirtualCardResponse Source: https://docs.fluz.app/api-reference/types/unlock-virtual-card-response **Object** *No description provided in the schema yet.* ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # UploadSoleProprietorshipDocumentResponse Source: https://docs.fluz.app/api-reference/types/upload-sole-proprietorship-document-response Response from uploading sole proprietorship document. **Object** Response from uploading sole proprietorship document. ## Fields The public URL of the uploaded document. # User Source: https://docs.fluz.app/api-reference/types/user Represents the User who has access to the account. **Object** Represents the User who has access to the account. ## Fields The userId associated with the account. The first name of the user. The last name of the user. # UserAddress Source: https://docs.fluz.app/api-reference/types/user-address UserAddress represents a User Address record. **Object** UserAddress represents a User Address record. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # UserBalances Source: https://docs.fluz.app/api-reference/types/user-balances UserBalances represents the balances of a user's rewards, cash, and gift card cash. **Object** UserBalances represents the balances of a user's rewards, cash, and gift card cash. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # UserCashBalance Source: https://docs.fluz.app/api-reference/types/user-cash-balance UserCashBalance represents a user's cash balance account. **Object** UserCashBalance represents a user's cash balance account. ## Fields Unique identifier for the user cash balance. Total cash balance. Available cash balance. Lifetime cash balance (cumulative total ever deposited). Custom nickname for the balance. Status of the cash balance account. Date and time when the account was created. # UserCashBalanceConnection Source: https://docs.fluz.app/api-reference/types/user-cash-balance-connection Paginated response for cash balances query. **Object** Paginated response for cash balances query. ## Fields List of cash balances. Total count of cash balances matching the filter. Whether there are more results available. # UserCashBalanceDetail Source: https://docs.fluz.app/api-reference/types/user-cash-balance-detail UserCashBalanceDetail represents a user's cash balance account with additional details. **Object** UserCashBalanceDetail represents a user's cash balance account with additional details. ## Fields Unique identifier for the user cash balance. Total cash balance. Available cash balance. Lifetime cash balance (cumulative total ever deposited). Custom nickname for the balance. Status of the cash balance account. Whether cash balance is the default one. Date and time when the account was created. Date and time when the account was updated. # UserLookupResult Source: https://docs.fluz.app/api-reference/types/user-lookup-result Represents a user recipient. **Object** Represents a user recipient. ## Fields The account ID of the recipient. Display name for the recipient. Format: "First Last" # UserPurchase Source: https://docs.fluz.app/api-reference/types/user-purchase UserPurchase represents a record of a purchase made by a user, detailing the payment methods and other relevant information. **Object** UserPurchase represents a record of a purchase made by a user, detailing the payment methods and other relevant information. ## Fields The ID associated with the purchase. The display ID of the purchase. The ID of the Bank Card used to make the purchase. The ID of the Bank Account used to make the purchase. The amount of the purchase. The amount of the balance applied. The amount of the cashback reward from the purchase. The ID of the PayPal used to make the purchase. The time the purchase was created. A connection to the gift card associated with the purchase. A connection to the virtual card associated with the purchase. The unique identifier for the fluz account. The unique identifier for the user who made the purchase. The memo associated with the purchase. The category associated with the purchase. URL of the attachment associated with this purchase. # VerifyUserInformationResponse Source: https://docs.fluz.app/api-reference/types/verify-user-information-response Represents the response returned from the verifyUserInformation mutation. **Object** Represents the response returned from the verifyUserInformation mutation. ## Fields The verification status. The response message if any. # VirtualCard Source: https://docs.fluz.app/api-reference/types/virtual-card VirtualCard represents a record of a virtual card generated by a user. **Object** VirtualCard represents a record of a virtual card generated by a user. ## Fields The ID associated with the virtual card. The ID of the user who created the virtual card. The cardholder name appeared on the virtual card. The expiration month of the virtual card. The expiration year of the virtual card. The last 4 digits of the virtual card number. The status of the virtual card. The type of the virtual card. The initial amount requested when the card was generated. The total amount spent on the virtual card. The time the virtual card was generated. A connection to the authorization setting of the virtual card. # VirtualCardAddressInfo Source: https://docs.fluz.app/api-reference/types/virtual-card-address-info VirtualCardAddressInfo provides the address information associated with a virtual card. **Object** VirtualCardAddressInfo provides the address information associated with a virtual card. ## Fields The street address. The second line of the billing address. The postal code. The city. The state. # VirtualCardAuthorizationSetting Source: https://docs.fluz.app/api-reference/types/virtual-card-authorization-setting VirtualCardAuthorizationSetting defines the authorization settings for a virtual card. **Object** VirtualCardAuthorizationSetting defines the authorization settings for a virtual card. ## Fields The unique identifier for the authorization setting of the virtual card. The unique identifier for the virtual card. The date when the card was locked. The daily spending limit for the card. The weekly spending limit for the card. The monthly spending limit for the card. The annual spending limit for the card. The lifetime spending limit for the card. The amount spent daily on the card. The amount spent weekly on the card. The amount spent monthly on the card. The amount spent annually on the card. The amount spent over the lifetime of the card. Whether the card is tokenized or not. Whether the card is locked by the user or not. Whether the card should be locked after the next use. The nickname of the card. # VirtualCardBalance Source: https://docs.fluz.app/api-reference/types/virtual-card-balance VirtualCardBalance provides information about virtual card balance. **Object** VirtualCardBalance provides information about virtual card balance. ## Fields Virtual Card ID. Current card spent amount. Remaining card balance. Spend limit for card. Card limit duration type. Default is Lifetime. # VirtualCardBulkOrder Source: https://docs.fluz.app/api-reference/types/virtual-card-bulk-order VirtualCardBulkOrder provides information about bulk creation order. **Object** VirtualCardBulkOrder provides information about bulk creation order. ## Fields Order ID. Status of the order. Created virtual card details. Total cards count. Successful card creation count. Failed card creation count. # VirtualCardDetails Source: https://docs.fluz.app/api-reference/types/virtual-card-details The VirtualCardDetails type represents the details of a virtual card. **Object** The VirtualCardDetails type represents the details of a virtual card. ## Fields The card ID. The card number. The expiry date in MMYY format. The CVV code. The card holder's name. The billing address. The authorization setting. # VirtualCardOffer Source: https://docs.fluz.app/api-reference/types/virtual-card-offer Represents the Bank identification numbers (BINs). **Object** Represents the Bank identification numbers (BINs). ## Fields Virtual Card offer ID. Virtual Card program name. The bank bin. The bank name. The bank program spent limits. The bank program earn rate. # VirtualCardTransaction Source: https://docs.fluz.app/api-reference/types/virtual-card-transaction Virtual card transaction type. **Object** Virtual card transaction type. ## Fields Unique identifier for the virtual card transaction. Raw merchant descriptor reported for the card transaction, such as "FACEBK \*8KJ88V5MR2". Null when no descriptor is associated with the transaction. *No description provided in the schema yet.* *No description provided in the schema yet.* The transaction amount, always expressed in USD. Null for rows where the underlying amount has not been settled (e.g. AVS-only decline records). *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* The merchant category code (MCC) associated with the transaction. Null when the transaction has no descriptor record or the MCC is unavailable. ISO country code of the merchant. Null when the transaction has no descriptor record or the merchant country is unavailable. ISO 4217 currency code of the original transaction (e.g. "USD" for domestic). Null when the original currency is unknown or not applicable to this row. The original transaction amount in the merchant's currency, expressed in that currency's minor units per ISO 4217 (e.g. cents for USD/HKD/EUR, yen for JPY, fils for KWD). Divide by the currency's decimal exponent to get the major-unit value. Null when originalCurrencyCode is null. The conversion rate applied to convert the original currency to USD. Equals 1.0 for USD transactions. Null when originalCurrencyCode is null. # VirtualCardTransactions Source: https://docs.fluz.app/api-reference/types/virtual-card-transactions Virtual card transactions, grouped by virtual card ID. **Object** Virtual card transactions, grouped by virtual card ID. ## Fields *No description provided in the schema yet.* *No description provided in the schema yet.* # AccountType Source: https://docs.fluz.app/api-reference/types/account-type Enum describing the types of accounts. **Enum** Enum describing the types of accounts. ## Values Account is a consumer/personal account. Account is a business account. # AddBankCardInput Source: https://docs.fluz.app/api-reference/types/add-bank-card-input Input type for adding a bank card to the user's wallet. **Input object** Input type for adding a bank card to the user's wallet. ## Input fields The card number. The expiration month in the format MM. The expiration year in the format YYYY. The three- or four-digit security numbers. The name of the cardholder. Create the card with a new address. Create the card with an existing user address. Create the card with preferred mcc category. Create the card with nickname. # AddVirtualCardAddressInput Source: https://docs.fluz.app/api-reference/types/add-virtual-card-address-input Input type for saving a billing address before issuing virtual cards. **Input object** Input type for saving a billing address before issuing virtual cards. ## Input fields Billing address to save on the target account for the caller or authorized cardholder. Optional authorized user ID (UAC role assignment ID). When provided, the address is saved for that user's underlying user\_id while remaining attached to the caller's account. # ApprovalRequestAction Source: https://docs.fluz.app/api-reference/types/approval-request-action Approval action used by approve/decline mutations. **Enum** Approval action used by approve/decline mutations. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # ApprovalRequestApproverStatus Source: https://docs.fluz.app/api-reference/types/approval-request-approver-status Approval approver status. **Enum** Approval approver status. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ApprovalRequestStatus Source: https://docs.fluz.app/api-reference/types/approval-request-status Approval request status. **Enum** Approval request status. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # BankAccountStatus Source: https://docs.fluz.app/api-reference/types/bank-account-status BankAccountStatus represents the current status of a bank account. **Enum** BankAccountStatus represents the current status of a bank account. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # BankAccountType Source: https://docs.fluz.app/api-reference/types/bank-account-type BankAccountType represents the type of bank account. **Enum** BankAccountType represents the type of bank account. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # BankCardStatus Source: https://docs.fluz.app/api-reference/types/bank-card-status BankCardStatus represents the status of bank card. **Enum** BankCardStatus represents the status of bank card. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # BankCardType Source: https://docs.fluz.app/api-reference/types/bank-card-type BankCardType represents the type of bank card. **Enum** BankCardType represents the type of bank card. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # BarcodeTypeEnum Source: https://docs.fluz.app/api-reference/types/barcode-type-enum BarcodeTypeEnum defines the barcode type of the offer. **Enum** BarcodeTypeEnum defines the barcode type of the offer. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # BulkPaginationInput Source: https://docs.fluz.app/api-reference/types/bulk-pagination-input Pagination input for bulk API listings. **Input object** Pagination input for bulk API listings. The bulk API allows larger pages than the rest of the API: the maximum (and default) page size is 100. ## Input fields The number of items to return per page. Maximum and default are 100. The number of items to skip. # BulkTargetErrorCode Source: https://docs.fluz.app/api-reference/types/bulk-target-error-code Why a bulk operation could not be performed for one target. **Enum** Why a bulk operation could not be performed for one target. Failures are always per-target: one user's failure never fails the rest of the request. ## Values The id does not correspond to a user currently connected to your application. The id is not a valid identifier. The user has not granted the scopes this operation requires. The user's grant does not permit this operation on the requested account. # BulkTargetMode Source: https://docs.fluz.app/api-reference/types/bulk-target-mode How a bulk operation selects its target users. **Enum** How a bulk operation selects its target users. ## Values Every user currently connected to your application. Only the users identified in the targets list. # BulkTargetSpecInput Source: https://docs.fluz.app/api-reference/types/bulk-target-spec-input Selects the users a bulk operation applies to. **Input object** Selects the users a bulk operation applies to. A maximum of 100 targets can be addressed per synchronous request. ## Input fields *No description provided in the schema yet.* The externalReferenceIds of the users to target. Required for SELECTED mode; ignored for ALL\_CONNECTED. Duplicates are removed; result order follows this list. # BulkUpdateErpTransactionMetadataItem Source: https://docs.fluz.app/api-reference/types/bulk-update-erp-transaction-metadata-item **Input object** *No description provided in the schema yet.* ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* # BusinessAccountUsageType Source: https://docs.fluz.app/api-reference/types/business-account-usage-type Business account usage types. **Enum** Business account usage types. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # BusinessLegalAddressInput Source: https://docs.fluz.app/api-reference/types/business-legal-address-input Business legal address input. **Input object** Business legal address input. ## Input fields Street address line 1. Street address line 2 (optional). City. State/province Postal code. Country name. Must be a valid country name from ISO 3166 standard that is allowed for KYB (Know Your Business) registration. Certain countries are restricted and not allowed for business registration (e.g., North Korea, Iran, Russia, Cuba, Syria, and others). Examples of allowed countries: "Canada", "Germany", "France". # BusinessLookupInput Source: https://docs.fluz.app/api-reference/types/business-lookup-input Input for looking up business recipients by company name. **Input object** Input for looking up business recipients by company name. ## Input fields Lookup by company name (case-insensitive). Searches both business name and DBA name using OR logic. Returns all matching businesses. # BusinessOwnerInput Source: https://docs.fluz.app/api-reference/types/business-owner-input Business owner information for registration. **Input object** Business owner information for registration. ## Input fields Owner's first name. Owner's last name. Owner's email address. Must be a registered Fluz user email for primary owner. Owner's title in the company. Percentage of ownership (0-100). Last 4 digits of SSN. Owner's date of birth in MM/DD/YYYY format (e.g., 02/28/1975). Owner's phone number: minimum 10 digits, digits only (any country). Owner's address (must be US-based). # BusinessStructureType Source: https://docs.fluz.app/api-reference/types/business-structure-type Business structure types supported for registration. **Enum** Business structure types supported for registration. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # CashBalanceAvailabilityType Source: https://docs.fluz.app/api-reference/types/cash-balance-availability-type Enum defining the types of availability for a cash balance. **Enum** Enum defining the types of availability for a cash balance. ## Values The deposit can be settled immediately. Standard processing times applies. # CashBalanceDepositStatus Source: https://docs.fluz.app/api-reference/types/cash-balance-deposit-status Enum representing the status of a cash balance deposit. **Enum** Enum representing the status of a cash balance deposit. ## Values The deposit is available in the user's account. The deposit attempt has failed. The deposit is currently being processed. The deposit was refunded. The deposit was reversed after being credited. The deposit is under review. # CashBalanceDepositType Source: https://docs.fluz.app/api-reference/types/cash-balance-deposit-type Enum representing different types of cash balance deposits. **Enum** Enum representing different types of cash balance deposits. ## Values A regular cash balance deposit. A deposit held in reserve balance. A *non-withdrawable* deposit for gift card balance. # CloRateTypeEnum Source: https://docs.fluz.app/api-reference/types/clo-rate-type-enum Indicates the type of rate active for a specific period of a Card Linked Offer. **Enum** Indicates the type of rate active for a specific period of a Card Linked Offer. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # CloseUserCashBalanceFundingSource Source: https://docs.fluz.app/api-reference/types/close-user-cash-balance-funding-source CloseUserCashBalanceFundingSource represents new funding source for all user cash balance-associated virtual cards. **Input object** CloseUserCashBalanceFundingSource represents new funding source for all user cash balance-associated virtual cards. ## Input fields The bank account ID. The spend account ID. Primary Funding Source for Virtual Card. # CloseUserCashBalanceInput Source: https://docs.fluz.app/api-reference/types/close-user-cash-balance-input Input type for closing a user cash balance account. **Input object** Input type for closing a user cash balance account. ## Input fields A unique client generated UUID to ensure a request is processed only once. Unique identifier for the user's *closing* cash balance. New funding source for all user cash balance-associated virtual cards Whether we should lock all user cash balance-associated virtual cards Unique identifier for the user's *transfer destination* cash balance. # CompletePlaidLinkInput Source: https://docs.fluz.app/api-reference/types/complete-plaid-link-input Input submitted by the frontend after Plaid Link onSuccess returns a public token. **Input object** Input submitted by the frontend after Plaid Link onSuccess returns a public token. For relink, include the same platformItemId used to create the Link token. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* # CreatePlaidLinkAddressInput Source: https://docs.fluz.app/api-reference/types/create-plaid-link-address-input Input for creating the address required to complete a Plaid bank link. **Input object** Input for creating the address required to complete a Plaid bank link. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* # CreatePlaidLinkTokenInput Source: https://docs.fluz.app/api-reference/types/create-plaid-link-token-input Input for creating a Plaid Link token. **Input object** Input for creating a Plaid Link token. Omit platformItemId for a new link; provide it to relink a disconnected connection. For web Link, omit deviceOs. For native Link, pass IOS or ANDROID to enable Plaid OAuth redirect handling. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* # CreateTransferInput Source: https://docs.fluz.app/api-reference/types/create-transfer-input Represents the request payload for creating a transfer. **Input object** Represents the request payload for creating a transfer. The sender is always determined from the authentication credentials. ## Input fields A unique client-generated UUID to ensure a request is processed only once. The amount to transfer (must be positive). The destination for the transfer. For public applications, defaults to the application operator if omitted. Bank card ID to fund the transfer. At most one funding source allowed. Bank account ID to fund the transfer via ACH. At most one funding source allowed. PayPal vault ID to fund the transfer via PayPal. At most one funding source allowed. Optional memo to attach to this transfer transaction. Optional category name to attach to this transfer transaction. ID of a previously uploaded attachment (PDF or PNG). Use the /api/v1/file-upload/transaction-memo-attachment endpoint to upload. # CreateUserCashBalanceInput Source: https://docs.fluz.app/api-reference/types/create-user-cash-balance-input Input type for creating a new user cash balance account. **Input object** Input type for creating a new user cash balance account. ## Input fields Nickname for the cash balance account. # CreateVirtualCardBulkOrderInput Source: https://docs.fluz.app/api-reference/types/create-virtual-card-bulk-order-input **Input object** *No description provided in the schema yet.* ## Input fields The Offer Id of Virtual Card Offer. This offer will be applied to all cards created in this order. A list of configurations for the virtual cards to be created. Each item in this list represents a group of one or more cards with identical settings. The total number of cards across all items cannot exceed 100. # CreateVirtualCardInput Source: https://docs.fluz.app/api-reference/types/create-virtual-card-input Input type for creating virtual cards. **Input object** Input type for creating virtual cards. ## Input fields A unique client generated UUID to ensure a request is processed only once. The maximum amount that you can charge to the card. You will only be charged for the amount you actually used. Card limit duration type. Default Lifetime. The date when the card will be locked. The default is 47 months. The setting to lock the card after next use. The default is false. The card's nickname. Primary Funding Source for Virtual Card. The unique identifier for the bank account. The Offer Id of Virtual Card Offer. Use getVirtualCardOffers to fetch a list of active offers. The unique identifier of the user cash balance id A new billing address to register with the card program before issuing the card. A UserAddress (type: BILLING) is created if one does not already exist with the same street / city / state / postal code. Ignored when userAddressId is also provided. An existing UserAddress ID belonging to the caller's account to use as the billing address. Takes precedence over billingAddress when both are provided. When false, the card will not draw from the prepaid (gift card) balance. Defaults to true to preserve the existing behavior where the card may be funded by prepaid funds in addition to the specified spend account. When false, the card will not draw from the rewards balance. Defaults to true to preserve the existing behavior where the card may be funded by rewards in addition to the specified spend account. Optional authorized user ID (UAC role assignment ID) to create the virtual card on behalf of. The assignment must exist on the caller's account and be ACTIVE. When provided, the downstream access token is issued for the underlying user so the resulting card is created on their behalf. An optional note to attach to the transaction. An optional category name. A matching category will be found or created for the account. # DeactivateVCShareLinksInput Source: https://docs.fluz.app/api-reference/types/deactivate-vcshare-links-input Input type for deactivateVCShareLinks. **Input object** Input type for deactivateVCShareLinks. ## Input fields The list of batch ID's to deactivate. Each share link included in these batches will be deactivated. The list of display ID's to deactivate. Only the share links with these display ID's will be deactivated. # DeclinedTransactionFilterInput Source: https://docs.fluz.app/api-reference/types/declined-transaction-filter-input Input filter for querying transactions. **Input object** Input filter for querying transactions. ## Input fields Filter by specific transaction record IDs. Filter by declined transaction status. Filter by exact amount. Filter by minimum amount (greater than or equal). Filter by maximum amount (less than or equal). Filter by exact fluz amount. Filter by minimum fluz amount (greater than or equal). Filter by maximum fluz amount (less than or equal). Filter by creation date (greater than or equal). Filter by creation date (less than or equal). Filter by update date (greater than or equal). Filter by update date (less than or equal). Filter by merchant IDs. Filter by merchant names (destination field). Filter by transaction types. Filter by channel. Filter by transaction category. Filter by virtual card program. Filter by specific virtual card IDs. Filter by funding source names. Filter by spend account IDs. Filter by liability ID (for bill payments). # DeclinedTransactionStatus Source: https://docs.fluz.app/api-reference/types/declined-transaction-status DeclinedTransactionStatus represents the current state of a transaction. **Enum** DeclinedTransactionStatus represents the current state of a transaction. ## Values Transaction has been declined. Transaction has failed. # DeleteBankCardInput Source: https://docs.fluz.app/api-reference/types/delete-bank-card-input Input type for deleting a bank card. **Input object** Input type for deleting a bank card. ## Input fields The ID of the bank card to delete. # DeliveryFormatType Source: https://docs.fluz.app/api-reference/types/delivery-format-type DeliveryFormatType defines the delivery format of the offer. **Enum** DeliveryFormatType defines the delivery format of the offer. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # DepositCashBalanceInput Source: https://docs.fluz.app/api-reference/types/deposit-cash-balance-input Input type for depositing cash balance, specifying the amount and deposit type. **Input object** Input type for depositing cash balance, specifying the amount and deposit type. ## Input fields A unique client generated UUID to ensure a request is processed only once. The amount deposited. Type of the deposit being made. A four-digit number that classifies a business by the type of products or services it offers Identifier of the bank account for the deposit. Identifier of the bank card for the deposit. Identifier of the PayPal ID for the deposit. Identifier of the UserCashBalance for the deposit. Ignored if depositType is not CASH\_BALANCE Memo for the deposit. Limited to 255 characters. Optional category name to attach to this deposit transaction. ID of a previously uploaded attachment (PDF or PNG). Use the /api/v1/file-upload/transaction-memo-attachment endpoint to upload. # EditVirtualCardInput Source: https://docs.fluz.app/api-reference/types/edit-virtual-card-input Input type for edit virtual card. **Input object** Input type for edit virtual card. ## Input fields The virtual card to update. The maximum amount that you can charge to the card. You will only be charged for the amount you actually used. Card limit duration type. The date when the card will be locked. The setting to lock the card after next use. The card's nickname. The bank account ID. The spend account ID. Primary Funding Source for Virtual Card. # ErpReferenceItemFilter Source: https://docs.fluz.app/api-reference/types/erp-reference-item-filter **Input object** *No description provided in the schema yet.* ## Input fields Case-insensitive substring match on the item's name. Defaults to true (active items only). Chart-of-accounts entries only. Chart-of-accounts entries only. Defaults to NAME. Defaults to ASC. # ErpReferenceItemSortKey Source: https://docs.fluz.app/api-reference/types/erp-reference-item-sort-key **Enum** *No description provided in the schema yet.* ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ErpTransactionMetadataFilter Source: https://docs.fluz.app/api-reference/types/erp-transaction-metadata-filter **Input object** *No description provided in the schema yet.* ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* Defaults to UPDATED\_AT. Defaults to DESC. # ErpTransactionMetadataSortKey Source: https://docs.fluz.app/api-reference/types/erp-transaction-metadata-sort-key **Enum** *No description provided in the schema yet.* ## Values *No description provided in the schema yet.* # FilterByInput Source: https://docs.fluz.app/api-reference/types/filter-by-input Input for applying fine-grained filters on offer attributes. **Input object** Input for applying fine-grained filters on offer attributes. This will filter the offers *within* a merchant. If all of a merchant's offers are filtered out, the merchant itself will be excluded. ## Input fields Filters gift card offers by their delivery format. # GenerateVCShareLinksInput Source: https://docs.fluz.app/api-reference/types/generate-vcshare-links-input Input type for generateVCShareLinks. **Input object** Input type for generateVCShareLinks. ## Input fields The limit on the card you want to share. The offer ID for the virtual card. The number of days the card should be valid for. The number of share links to generate. The method you want to share cards. The list of phone numbers to send the share link to via SMS. The list of email addresses to send the share link to. Known Fluz user IDs to bind as share-link recipients when shareMethod is EXISTING\_USER. Required length must equal quantity. Mutually exclusive with recipientRegistrations. Inline pre-register payloads when shareMethod is REGISTER\_USER. TGS creates or reuses placeholder users (no seat) before generating links. Required length must equal quantity. Mutually exclusive with recipientUserIds. The spend account you want to use to fund the virtual card. Existing virtual card to bind to the hosted link. When provided, claim grants access instead of issuing a new card. Set whether to use your Prepayment Balance as an additional funding source. The default is false. Set whether to use your Fluz Rewards Balance as an additional funding source. The default is false. # GetCardProvisioningUrlInput Source: https://docs.fluz.app/api-reference/types/get-card-provisioning-url-input **Input object** *No description provided in the schema yet.* ## Input fields The card to provision — the `virtualCardId` returned by `createVirtualCard`. Must be a UUIDv4, must belong to the caller's account, and must be ACTIVE. Provide exactly one of `virtualCardId` or `offerId`. Prefer this one: it names the card unambiguously no matter how many cards the account holds on the offer. The virtual card offer to provision. Must be a UUIDv4, active, tokenization-eligible, and accessible to the caller's account. Provide exactly one of `virtualCardId` or `offerId`. An offer id only identifies a card while the account holds at most one active card on that offer; once there are several, this query fails with `VirtualCard.AMBIGUOUS_CARD_SELECTION` and you must pass `virtualCardId`. Optional platform hint. Defaults to IOS. See ProvisioningPlatform for what this value controls (it does NOT lock the URL to a single platform — iOS and Android opens are auto-routed correctly via platform-aware redirects). # GetOfferQuoteInput Source: https://docs.fluz.app/api-reference/types/get-offer-quote-input **Input object** *No description provided in the schema yet.* ## Input fields Human readable unique identifier for merchant. Can be found in merchant list. The purchase amount The payment method to be used for the purchase. Default is FLUZPAY # GetVCShareLinksInput Source: https://docs.fluz.app/api-reference/types/get-vcshare-links-input Input type for generateVCShareLinks. **Input object** Input type for generateVCShareLinks. ## Input fields Specify the share object status to filter on. The list of batch ID's to retrieve. Each share link included in these batches will be fetched. The list of display ID's to retrieve. Only the share link with these display ID's will be fetched. # GetVirtualCardBalanceInput Source: https://docs.fluz.app/api-reference/types/get-virtual-card-balance-input Input type for get virtual card balance. **Input object** Input type for get virtual card balance. ## Input fields The virtual card ids to fetch balances. # GetVirtualCardBulkOrderStatusInput Source: https://docs.fluz.app/api-reference/types/get-virtual-card-bulk-order-status-input Input type for get bulk virtual card creation order. **Input object** Input type for get bulk virtual card creation order. ## Input fields *No description provided in the schema yet.* # GetVirtualCardOffersInput Source: https://docs.fluz.app/api-reference/types/get-virtual-card-offers-input Input type for get virtual card offers. **Input object** Input type for get virtual card offers. ## Input fields Specify if virtual card offers are brand locked. Specify virtual card network type. Specify virtual card network type. # GetVirtualCardTransactionsInput Source: https://docs.fluz.app/api-reference/types/get-virtual-card-transactions-input Input type for fetching transactions for multiple virtual cards. **Input object** Input type for fetching transactions for multiple virtual cards. ## Input fields A list of virtual card IDs to fetch transactions for. When omitted, transactions are returned across all cards belonging to the authenticated account, and pagination defaults to a limit of 100 transactions across the account if paginate.limit is not provided. Filters to apply to the transaction data, such as date range or transaction type. Pagination settings for the results, including offset and limit. When virtualCardIds is provided, the limit/offset apply per card. When virtualCardIds is omitted, the limit/offset apply across the entire account in a single query. # GetWithdrawFeeEstimateInput Source: https://docs.fluz.app/api-reference/types/get-withdraw-fee-estimate-input Input for estimating withdrawal fees before submitting a withdrawal. **Input object** Input for estimating withdrawal fees before submitting a withdrawal. ## Input fields The amount to withdraw. The withdrawal method. The source balance from which to withdraw. Whether to estimate fees for an expedited withdrawal. # LockVirtualCardInput Source: https://docs.fluz.app/api-reference/types/lock-virtual-card-input Input type for lock virtual card. **Input object** Input type for lock virtual card. ## Input fields The virtual card to update. # MerchantInput Source: https://docs.fluz.app/api-reference/types/merchant-input Input type for getReferralUrl. **Input object** Input type for getReferralUrl. ## Input fields The unique identifier for the merchant. Human readable unique identifier for merchant. Can be found in merchant list. Merchant name # OfferTypesInput Source: https://docs.fluz.app/api-reference/types/offer-types-input Input type for getMerchants. **Input object** Input type for getMerchants. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* # OffsetInput Source: https://docs.fluz.app/api-reference/types/offset-input The page info. **Input object** The page info. ## Input fields The number of items to return per page. The maximum for limit is 20. The number of items to skip. # PlaidBankAccountFilterInput Source: https://docs.fluz.app/api-reference/types/plaid-bank-account-filter-input Optional filters for listing persisted Plaid bank accounts owned by the authenticated account. **Input object** Optional filters for listing persisted Plaid bank accounts owned by the authenticated account. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidBankAccountInput Source: https://docs.fluz.app/api-reference/types/plaid-bank-account-input Input for selecting one persisted Plaid bank account. **Input object** Input for selecting one persisted Plaid bank account. ## Input fields *No description provided in the schema yet.* # PlaidBankBalanceFilterInput Source: https://docs.fluz.app/api-reference/types/plaid-bank-balance-filter-input Optional filters for listing latest persisted Plaid bank balances. **Input object** Optional filters for listing latest persisted Plaid bank balances. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidBankTransactionFilterInput Source: https://docs.fluz.app/api-reference/types/plaid-bank-transaction-filter-input Input for listing persisted historical bank transactions populated by identity-service. **Input object** Input for listing persisted historical bank transactions populated by identity-service. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidLinkAddressInput Source: https://docs.fluz.app/api-reference/types/plaid-link-address-input Address attached to Plaid-linked bank accounts when identity-service requires one after Link. **Input object** Address attached to Plaid-linked bank accounts when identity-service requires one after Link. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PurchaseGiftCardInput Source: https://docs.fluz.app/api-reference/types/purchase-gift-card-input Input type for purchase gift card. **Input object** Input type for purchase gift card. ## Input fields A unique client generated UUID to ensure a request is processed only once. The unique identifier for the offer. The amount to purchase. The balance amount. The unique identifier for the bank account. The unique identifier of the bank card. The unique identifier of the PayPal account. The unique identifier of the exclusive rate. Human readable unique identifier for merchant. Can be found in merchant list. Use balance as the fallback payment method if the primary payment method fails. Defaults to true. The minimum reward rate to purchase if merchantSlug option is chosen. The unique identifier of the user cash balance id used for this purchase Optional memo to attach to this purchase transaction. Optional category name to attach to this purchase transaction. ID of a previously uploaded attachment (PDF or PNG). Use the /api/v1/file-upload/transaction-memo-attachment endpoint to upload. # RedeemFluzGiftCardInput Source: https://docs.fluz.app/api-reference/types/redeem-fluz-gift-card-input Input type for redeeming a Fluz Gift Card to the user's account. **Input object** Input type for redeeming a Fluz Gift Card to the user's account. ## Input fields A unique client generated UUID to ensure a request is processed only once. The Fluz Gift Card code (as printed on or delivered with the card). # RegisterBusinessInput Source: https://docs.fluz.app/api-reference/types/register-business-input Input for registering a new business. **Input object** Input for registering a new business. ## Input fields Legal business name. Doing Business As name (optional). Business structure type. Business legal address. State of incorporation. Tax ID / EIN Number. Must be in format XX-XXXXXXX (2 digits, hyphen, 7 digits). URL to sole proprietorship documentation (required if business structure is SOLE\_PROPRIETORSHIP). Use the REST endpoint POST /api/v1/file-upload/sole-proprietorship-document to upload the file first and get the URL. Business category ID (from getBusinessCategories query). Business sub-category ID (from getBusinessCategories query). Brief description of the nature of business (optional). Business website URL (optional). Business account usage types. Must be one of the predefined enum values. If businessAccountUsage is not provided or is empty, businessAccountUsageOther must be filled. Other business account usage description. Required if businessAccountUsage is not provided or is empty. List of business owners. Owners are sorted by ownership percentage, and the owner with the highest percentage is treated as primary owner. Primary owner must be a registered Fluz user. # RemovePlaidBankInstitutionInput Source: https://docs.fluz.app/api-reference/types/remove-plaid-bank-institution-input Input for removing a connected Plaid bank institution. **Input object** Input for removing a connected Plaid bank institution. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* # RequestAccountTransferInput Source: https://docs.fluz.app/api-reference/types/request-account-transfer-input Represents the request payload for account-transfer approval requests. **Input object** Represents the request payload for account-transfer approval requests. Mirrors createTransfer semantics; the sender is determined from authentication credentials. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # RequestDocumentVerificationLinkInput Source: https://docs.fluz.app/api-reference/types/request-document-verification-link-input Input type for requesting user verification **Input object** Input type for requesting user verification ## Input fields Indicates whether the user has given consent for verification Whether to prefill the verification form with existing user data Whether to reset an existing verification session Whether to ignore the max attempts check The user's first name The user's last name The street address line 1 The street address line 2 The address city The address region/state The postal code The address country in ISO 3166-1 alpha-2 format The user's date of birth in YYYY-MM-DD format (RFC 3339 Section 5.6) The user's email address The user's phone number in E.164 format # RequestGiftCardPurchaseInput Source: https://docs.fluz.app/api-reference/types/request-gift-card-purchase-input **Input object** *No description provided in the schema yet.* ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # RequestInternalTransferInput Source: https://docs.fluz.app/api-reference/types/request-internal-transfer-input **Input object** *No description provided in the schema yet.* ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # RequestReimbursementInput Source: https://docs.fluz.app/api-reference/types/request-reimbursement-input **Input object** *No description provided in the schema yet.* ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # RequestVirtualCardInput Source: https://docs.fluz.app/api-reference/types/request-virtual-card-input **Input object** *No description provided in the schema yet.* ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # RequestVirtualCardLimitChangeInput Source: https://docs.fluz.app/api-reference/types/request-virtual-card-limit-change-input **Input object** *No description provided in the schema yet.* ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # SetBackupFundingSourceInput Source: https://docs.fluz.app/api-reference/types/set-backup-funding-source-input Input type for setting an account's backup funding source. **Input object** Input type for setting an account's backup funding source. The bank card must belong to the account and be ACTIVE. ## Input fields The ID of the bank card to set as the backup funding source. # SetPrimaryFundingSourceInput Source: https://docs.fluz.app/api-reference/types/set-primary-funding-source-input Input type for setting an account's primary funding source. **Input object** Input type for setting an account's primary funding source. The bank account must belong to the account and be ENABLED. ## Input fields The ID of the bank account to set as the primary funding source. # SetVirtualCardPINInput Source: https://docs.fluz.app/api-reference/types/set-virtual-card-pininput Input type for set Virtual Card PIN. **Input object** Input type for set Virtual Card PIN. ## Input fields The PIN to set on the cards. Must match your user PIN. # ShareLinkRecipientRegistrationInput Source: https://docs.fluz.app/api-reference/types/share-link-recipient-registration-input Input type for generateVCShareLinks. **Input object** Input type for generateVCShareLinks. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # TransactionFilterInput Source: https://docs.fluz.app/api-reference/types/transaction-filter-input Input filter for querying transactions. **Input object** Input filter for querying transactions. ## Input fields Filter by specific transaction record IDs. Filter by transaction status. Filter by exact amount. Filter by minimum amount (greater than or equal). Filter by maximum amount (less than or equal). Filter by exact cashback amount. Filter by minimum cashback amount. Filter by maximum cashback amount. Filter by exact cashback percentage. Filter by minimum cashback percentage. Filter by maximum cashback percentage. Filter by exact fee amount. Filter by minimum fee amount. Filter by maximum fee amount. Filter by creation date (greater than or equal). Filter by creation date (less than or equal). Filter by update date (greater than or equal). Filter by update date (less than or equal). Filter by merchant IDs. Filter by merchant names (destination field). Filter by transaction types. Filter by channel. Filter by transaction category. Filter by virtual card program. Filter by specific virtual card IDs. Filter by funding source names. Filter by spend account IDs. Filter by reference ID. Filter by liability ID (for bill payments). # TransferDestination Source: https://docs.fluz.app/api-reference/types/transfer-destination Identifies the destination for a transfer. **Input object** Identifies the destination for a transfer. Provide either accountId or externalReferenceId (not both). ## Input fields The destination account ID. Alternative to accountId. Identifies the destination by an external reference ID. Target a specific cash balance on the destination account. Must belong to the destination account. If omitted, the system resolves the appropriate cash balance automatically. # TransferInternalBalanceInput Source: https://docs.fluz.app/api-reference/types/transfer-internal-balance-input Input type for a transfer between user cash balance accounts. **Input object** Input type for a transfer between user cash balance accounts. ## Input fields A unique client generated UUID to ensure a request is processed only once. The amount to transfer. Unique identifier for the user's *source* cash balance. Unique identifier for the user's *destination* cash balance. Memo for the transfer. Limited to 255 characters. Optional category name to attach to this transfer transaction. ID of a previously uploaded attachment (PDF or PNG). Use the /api/v1/file-upload/transaction-memo-attachment endpoint to upload. # UnlockVirtualCardInput Source: https://docs.fluz.app/api-reference/types/unlock-virtual-card-input Input type for unlock virtual card. **Input object** Input type for unlock virtual card. ## Input fields The virtual card to update. # UpdateBankCardNicknameInput Source: https://docs.fluz.app/api-reference/types/update-bank-card-nickname-input Input type for updating a bank card's nickname. **Input object** Input type for updating a bank card's nickname. ## Input fields The ID of the bank card to update. The nickname for the bank card. # UpdateBankCardPreferredMerchantCategoryCodeInput Source: https://docs.fluz.app/api-reference/types/update-bank-card-preferred-merchant-category-code-input Input type for updating a bank card's preferred merchant category code. **Input object** Input type for updating a bank card's preferred merchant category code. ## Input fields The ID of the bank card to update. The preferred merchant category code for the bank card. # UpdateErpTransactionMetadataInput Source: https://docs.fluz.app/api-reference/types/update-erp-transaction-metadata-input Optional ERP-aware categorization for this transaction (e.g. **Input object** Optional ERP-aware categorization for this transaction (e.g. QuickBooks category, vendor, customer, billable status, and memo). Omit this field, or pass null, to leave ERP categorization unchanged. An empty object is not a valid update — omit the field entirely instead. Within the object: omit a field to leave it unchanged, pass null on a \*ReferenceItemId field to clear it, or pass a value to set it. Use categoryName, vendorName, or customerName to create or select an entity by name rather than by ID. These fields are ignored when the corresponding \*ReferenceItemId field is provided. This memo is separate from the top-level memo field and has its own length limit (up to 4000 characters, vs. 255 for the top-level memo). Setting one does not affect the other. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # UpdateTransactionMetadataInput Source: https://docs.fluz.app/api-reference/types/update-transaction-metadata-input Input for updating transaction metadata (memo, category, attachment). **Input object** Input for updating transaction metadata (memo, category, attachment). Only provided fields are updated — omitted fields are left unchanged. Set a field to null to clear it. ## Input fields The record ID of the transaction to update. User-defined memo to attach to this transaction. Max 255 characters. Set to null to clear. Category name to attach to this transaction. Set to null to clear. ID of a previously uploaded attachment (PDF or PNG). Use the /api/v1/file-upload/transaction-memo-attachment endpoint to upload. Set to null to clear. Optional ERP-aware categorization to apply in the same call. See UpdateErpTransactionMetadataInput for the full null/omission semantics. If this portion fails, the whole call fails and nothing is updated. # UpdateUserCashBalanceInput Source: https://docs.fluz.app/api-reference/types/update-user-cash-balance-input Input type for updating a user cash balance account. **Input object** Input type for updating a user cash balance account. ## Input fields Identifier of the UserCashBalance that needs to be updated. Nickname for the cash balance account. # UserAddressInput Source: https://docs.fluz.app/api-reference/types/user-address-input Input type for adding an address to the user. **Input object** Input type for adding an address to the user. ## Input fields *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # UserCashBalanceFilterInput Source: https://docs.fluz.app/api-reference/types/user-cash-balance-filter-input Input filter for querying cash balances. **Input object** Input filter for querying cash balances. ## Input fields Filter by specific cash balance IDs. Filter by cash balance nicknames. Filter by cash balance status. Whether cash balance is the default one. Filter by creation date (greater than or equal). Filter by creation date (less than or equal). Filter by update date (greater than or equal). Filter by update date (less than or equal). # UserLookupInput Source: https://docs.fluz.app/api-reference/types/user-lookup-input Input for looking up a user recipient by a specific identifier. **Input object** Input for looking up a user recipient by a specific identifier. Exactly one identifier must be provided. ## Input fields Lookup by phone number (E.164 format recommended, e.g., +14155551234). 10-digit US numbers are automatically normalized. Lookup by email address (case-insensitive). # UserPurchaseFilterInput Source: https://docs.fluz.app/api-reference/types/user-purchase-filter-input Input filter type for getUserPurchases - Default [TOKEN_USER]. **Input object** Input filter type for getUserPurchases * Default \[TOKEN\_USER]. ## Input fields *No description provided in the schema yet.* # USOwnerAddressInput Source: https://docs.fluz.app/api-reference/types/usowner-address-input US-based address for business owner (required to be US-based). **Input object** US-based address for business owner (required to be US-based). ## Input fields Street address line 1. Street address line 2 (optional). City. State (required, must be a valid US state name, e.g., "California", "New York", "Texas"). Postal code (must be 5 digits for US ZIP code format). # VirtualCardBillingAddressInput Source: https://docs.fluz.app/api-reference/types/virtual-card-billing-address-input Input type for a new billing address when issuing a virtual card. **Input object** Input type for a new billing address when issuing a virtual card. All fields marked non-null are required by the card issuer (Highnote). ## Input fields Street address (e.g., "123 Main St"). PO boxes are not accepted by the issuer. Optional secondary line (e.g., apartment, suite). Country name. Currently only "United States" is supported by the card issuer. City name. State name (e.g., "New York") or two-letter US state code. 5-digit US ZIP code. Required by the card issuer. # VirtualCardOrderItemInput Source: https://docs.fluz.app/api-reference/types/virtual-card-order-item-input **Input object** *No description provided in the schema yet.* ## Input fields The number of virtual cards to create with this specific configuration. The maximum amount that you can charge to the card. You will only be charged for the amount you actually used. Card limit duration type. Default is Lifetime. The date when the card will be locked. The default is 47 months. The setting to lock the card after next use. The default is false. The card's nickname. This nickname will be applied to all cards in this item. Primary Funding Source for Virtual Card. The unique identifier for the bank account. The unique identifier of the user cash balance id A new billing address to register with the card program before issuing cards in this item. A UserAddress (type: BILLING) is created if one does not already exist with the same street / city / state / postal code. Ignored when userAddressId is also provided. An existing UserAddress ID belonging to the caller's account to use as the billing address. Takes precedence over billingAddress when both are provided. An optional note to attach to the transaction. An optional category name. A matching category will be found or created for the account. # VirtualCardTransactionFiltersInput Source: https://docs.fluz.app/api-reference/types/virtual-card-transaction-filters-input Input type for filtering virtual card transactions. **Input object** Input type for filtering virtual card transactions. ## Input fields A list of transaction types to filter by (e.g., PURCHASE, REFUND). Inclusive lower bound for the transaction date range (ISO 8601 timestamp). Inclusive upper bound for the transaction date range (ISO 8601 timestamp). # Withdraw Source: https://docs.fluz.app/api-reference/types/withdraw Withdraw represents a withdrawal transaction record. **Object** Withdraw represents a withdrawal transaction record. ## Fields Unique identifier for the withdrawal. The amount withdrawn. Fee charged for processing the withdrawal. Fee charged to the user for the withdrawal. Internal status of the withdrawal. User-friendly display status of the withdrawal. The withdrawal method used. The source balance from which funds were withdrawn. Date and time when the withdrawal was submitted. Date and time when the withdrawal was created. Date and time when the withdrawal was last updated. Identifier of the associated transaction log. External transaction identifier from payment gateway (for ACH). Payout identifier from payment gateway (for PayPal/Venmo). Email address associated with the withdrawal. Identifier of the bank account used (if applicable). Identifier of the user cash balance account withdrawn from. The seat id associated with the account. # WithdrawCashBalanceInput Source: https://docs.fluz.app/api-reference/types/withdraw-cash-balance-input **Input object** *No description provided in the schema yet.* ## Input fields A unique client generated UUID to ensure a request is processed only once. The amount to withdraw. The withdrawal method to use. The source balance from which to withdraw funds. Identifier of the bank account for ACH withdrawals. Identifier of the bank card for push-to-card withdrawals. Required when method is BANK\_CARD. The card must support OCT transactions. Identifier of the PayPal vault for PayPal withdrawals. Identifier of the Venmo account for Venmo withdrawals. Identifier of the specific cash balance account to withdraw from (required when source is CASH\_BALANCE). If true, process the withdrawal immediately (expedited). If false or omitted, the withdrawal is processed on a standard settlement schedule. Expedited withdrawals may incur different fees — use the getWithdrawFeeEstimate query to preview. Currently applicable to BANK\_CARD withdrawals. # WithdrawCashBalanceResponse Source: https://docs.fluz.app/api-reference/types/withdraw-cash-balance-response Response type for the withdrawCashBalance mutation, returning the withdrawal record and updated balances. **Object** Response type for the withdrawCashBalance mutation, returning the withdrawal record and updated balances. ## Fields List of withdrawal transaction records created (usually one, but can be multiple if split across seats). User's current balances after the withdrawal. # WithdrawFeeEstimate Source: https://docs.fluz.app/api-reference/types/withdraw-fee-estimate Estimated fees and net amount for a withdrawal. **Object** Estimated fees and net amount for a withdrawal. ## Fields The fee that will be charged for this withdrawal. The net amount the user will receive after fees. The fee percentage applied. The maximum fee cap in dollars, if applicable. The number of business days until settlement. Null for expedited withdrawals that settle immediately. # Authentication reference Source: https://docs.fluz.app/api-reference/authentication The exact request/response shapes for minting user access tokens with your API key. For the conceptual overview, see [Authentication](/concepts/authentication). This page documents the exact GraphQL contract for minting tokens on the transactional graph: | Environment | Endpoint | | ----------- | ---------------------------------------------------------------- | | Staging | `https://transactional-graph.staging.fluzapp.com/api/v1/graphql` | | Production | `https://transactional-graph.fluzapp.com/api/v1/graphql` | ## `generateUserAccessToken` Mint a user access token with your application's **API key**. The API key is sent in the `Authorization: Basic ` header — it is never passed as a GraphQL argument. The token is minted for a `userId` / `accountId` pair (shown alongside your API key in the Developer Console) and carries the scopes you request. ### Request ```http theme={null} POST /api/v1/graphql HTTP/1.1 Host: transactional-graph.staging.fluzapp.com Authorization: Basic Content-Type: application/json { "query": "mutation ($userId: UUID!, $accountId: UUID!, $scopes: [ScopeType!]!) { generateUserAccessToken(userId: $userId, accountId: $accountId, scopes: $scopes) { token scopes } }", "variables": { "userId": "", "accountId": "", "scopes": ["LIST_OFFERS", "PURCHASE_GIFTCARD"] } } ``` An optional `seatId: UUID` argument selects the seat used for transactions; it defaults to the most recently created seat. See the full argument reference at [generateUserAccessToken](/api-reference/mutations/generate-user-access-token). ### Response ```json theme={null} { "data": { "generateUserAccessToken": { "token": "eyJhbGciOi...", "scopes": ["LIST_OFFERS", "PURCHASE_GIFTCARD"] } } } ``` Attach the returned `token` as `Authorization: Bearer ` on subsequent requests. Tokens are short-lived JWTs — mint a new one when it expires (see [Replace an expired access token](/get-started/refresh-expired-access-token)). ### Requirements * The user must have granted your application the requested scopes; unknown or ungranted scopes cause the mutation to fail. * `PCI_COMPLIANCE` cannot be requested when generating a token — it is granted at the application level to PCI-compliant developers. ## Discovering `userId` / `accountId` Also authorized by `Basic `: * [`getApplicationUsers`](/api-reference/queries/get-application-users) — the users who have granted scopes to your application, with their accounts. * [`getAccountsByUserId`](/api-reference/queries/get-accounts-by-user-id) — all accounts for a given user. * [`getApplicationScopes`](/api-reference/queries/get-application-scopes) — the scopes available to your application. ## Customer accounts (OAuth platforms) If you are building a platform that operates on customer accounts, the customer first authorizes your app through the OAuth grant flow, and your server exchanges the resulting code at the token exchange endpoint — see [the OAuth grant flow](/client-facing-o-auth-grant-flow) and [Build a platform](/build-a-platform). ## Scopes See the scope table in [Authentication](/concepts/authentication#scopes). Unknown scopes cause the mutation to fail. # Errors Source: https://docs.fluz.app/api-reference/errors Error codes, HTTP status codes, and how to troubleshoot with request IDs. GraphQL responses use HTTP 200 for both successes and application-level errors. Inspect the `errors` array to know what happened. ## Error shape ```json theme={null} { "errors": [ { "message": "Offer out of stock", "extensions": { "code": "GC-0009", "path": ["purchaseGiftCard"], "requestId": "req_01H...Offer out of stock", "code": "GC-0009", "path": ["purchaseGiftCard"] } ] } ``` Error codes are namespaced by area: * `AUTH-*` — authentication and token issues * `BS-*` — general business / validation errors * `GC-*` — gift card operations * `VC-*` — virtual card operations ## Domain error codes Fluz uses prefixed error codes so you can branch on the failing subsystem. | Prefix | Domain | Example | | ----------- | -------------------------------- | ------------------------------------------------------------------------------- | | `AUTH-XXXX` | Authentication and authorization | Invalid token, missing scope | | `VC-XXXX` | Virtual cards | `VC-0025` — invalid or unsupported address | | `GC-XXXX` | Gift cards | `GC-0009` — offer out of stock; `GC-0002` — amount not in allowed denominations | | `BS-XXXX` | Balances and settlement | Insufficient funds, holds | Codes not in this list surface as generic validation or server errors. ## Troubleshooting 1. **Check scope on `AUTH-*`.** These almost always mean the token was minted without the required scope; mint a new token. 2. `AUTH-0002`**Re-query before retrying `GC-*`.** Offers and stock change frequently — pull a fresh offer and retry. 3. **Re-query before retrying business errors.** `GC-0009` (stock) and similar mean the world changed — don't retry with stale**Don't retry validation errors.** `VC-*` and `BS-*` codes with `4xx`-like intent will fail identically on retry until you fix the input or balance. 4. **Retry transient server errors with backoff and jitterRetry 5xx / network errors with jitter.** Money-moving mutations are de-duplicated; see [Idempotency](/concepts/idempotency). # Pagination Source: https://docs.fluz.app/api-reference/pagination Offset pagination, page-size caps, and the connection response shape. List operations across the API paginate with offset inputs and return connection-style objects. There are two page-size regimes depending on the surface. ## Request shape Pass an offset input in the operation's `paginate` argument: | Input | Used by | `limit` max | Fields | | ------------------------------------------------------------------- | --------------------------------------------------------------- | -------------------------- | ----------------- | | [`OffsetInput`](/api-reference/types/offset-input) | Standard listings (`getTransactions`, `getUserCashBalances`, …) | **20** | `limit`, `offset` | | [`BulkPaginationInput`](/api-reference/types/bulk-pagination-input) | Bulk API listings | **100** (also the default) | `limit`, `offset` | ```graphql theme={null} query { getTransactions( filter: { } paginate: { limit: 20, offset: 0 } ) { transactions { transactionId amount createdAt } totalCount hasNextPage } } ``` ## Response shape Paginated queries return a connection object with the items plus paging metadata — for example [`TransactionConnection`](/api-reference/types/transaction-connection): * the item list (e.g. `transactions`) * `totalCount` — total items matching the filter * `hasNextPage` — whether more results are available Other connection types follow the same pattern: [`UserCashBalanceConnection`](/api-reference/types/user-cash-balance-connection), [`DeclinedTransactionConnection`](/api-reference/types/declined-transaction-connection), and [`PlaidBankTransactionPage`](/api-reference/types/plaid-bank-transaction-page). ## Walking a full result set Increase `offset` by `limit` while `hasNextPage` is `true`: 1. Request `{ limit: 20, offset: 0 }`. 2. If `hasNextPage` is `true`, request `{ limit: 20, offset: 20 }`, then `40`, and so on. 3. Stop when `hasNextPage` is `false`. Results can shift between pages if new records are created while you're paging — transaction listings are ordered most-recent-first. For large exports, filter by a fixed date range so the underlying set is stable while you walk it. # Rate limits Source: https://docs.fluz.app/api-reference/rate-limits Complexity-based limits, quotas, and best practices for staying within them. Fluz applies fair-use rate limits on the transactional graph and the OAuth service. Limits are tuned to typical integration patterns; contact your account team if your production workload needs headroom. ## When you're throttled Rate-limited requests return an HTTP `429` on the transport, or a rate-limit error code inside the GraphQL `errors` array. Back off before retrying and add jitter to smooth bursts. ## Best practices 1. **Ask for less.** Trim your selection set to the fields you actually use. 2. **Batch reads.** One query that returns 100 items costs less than 100 queries returning one each. 3. **Use webhooks instead of polling.** See [Configure App Widget](/developers/configure-app-widget) for webhook setup. 4. **Retry with exponential backoff and jitter** — never in a tight loop. 5. **Refresh access tokens ahead of expiry** to avoid 401 → mint-token → retry cycles under load. # Upload sole proprietorship document Source: https://docs.fluz.app/api-reference/rest/upload-sole-proprietorship-document REST endpoint for uploading the supporting document required when registering a SOLE_PROPRIETORSHIP business. Most of the Fluz API is GraphQL — file upload is the exception. When [`registerBusiness`](/api-reference/mutations/register-business) is called with `businessStructure: SOLE_PROPRIETORSHIP`, upload the supporting document through this REST endpoint first, then pass the returned URL as `soleProprietorshipDocumentUrl` in [`RegisterBusinessInput`](/api-reference/types/register-business-input). ## Endpoint | Environment | URL | | ----------- | ------------------------------------------------------------------------------------------------------ | | Staging | `POST https://transactional-graph.staging.fluzapp.com/api/v1/file-upload/sole-proprietorship-document` | | Live | `POST https://transactional-graph.fluzapp.com/api/v1/file-upload/sole-proprietorship-document` | ## Request Send the document as `multipart/form-data`, authenticated with the same access token you use for the registration flow: ```bash theme={null} curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/file-upload/sole-proprietorship-document \ -H "Authorization: Bearer " \ -F "file=@business-license.pdf" ``` ## Response Returns the public URL of the uploaded document — see [`UploadSoleProprietorshipDocumentResponse`](/api-reference/types/upload-sole-proprietorship-document-response): ```json theme={null} { "url": "https://storage.fluzapp.com/documents/..." } ``` Supply this value in `soleProprietorshipDocumentUrl` when submitting `registerBusiness`. For the full registration walkthrough, including the KYB lifecycle, see [Business registration](/business-registration). # BasicUserCashBalance Source: https://docs.fluz.app/api-reference/types/basic-user-cash-balance BasicUserCashBalance interface **Interface** BasicUserCashBalance interface ## Fields Unique identifier for the user cash balance. Total cash balance. Available cash balance. Lifetime cash balance (cumulative total ever deposited). Custom nickname for the balance. Status of the cash balance account. Date and time when the account was created. # DateTime Source: https://docs.fluz.app/api-reference/types/date-time A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. **Scalar** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. Custom scalar. # ErpTransactionSyncStatus Source: https://docs.fluz.app/api-reference/types/erp-transaction-sync-status Sync state of a transaction's ERP metadata against the connected accounting provider. **Enum** Sync state of a transaction's ERP metadata against the connected accounting provider. ## Values Missing a required field (e.g. category) and cannot be synced yet. All required fields are present, ready for sync. Currently being exported to the ERP provider. Successfully exported to the ERP provider. The last export attempt failed. # GiftCardStatus Source: https://docs.fluz.app/api-reference/types/gift-card-status GiftCardStatus represents the current state of a gift card. **Enum** GiftCardStatus represents the current state of a gift card. ## Values The default status when a Gift Card is generated. The Gift Card has been marked as used by the user. The Gift Card is not currently active. The Gift Card has been marked as restricted. # ObjectOwnerObjectType Source: https://docs.fluz.app/api-reference/types/object-owner-object-type Supported object domains for metadata-only ownership. **Enum** Supported object domains for metadata-only ownership. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # OfferDenominationType Source: https://docs.fluz.app/api-reference/types/offer-denomination-type OfferDenominationType is the type of denomination of a specific offer. **Enum** OfferDenominationType is the type of denomination of a specific offer. ## Values VARIABLE means that the offer has a variable denomination. FIXED means that the offer has fixed value denominations. VARIABLENOCENTS means that the offer has a variable denomination but the value must be an integer. # OfferType Source: https://docs.fluz.app/api-reference/types/offer-type OfferType is the type of offer. **Enum** OfferType is the type of offer. ## Values GIFT\_CARD\_OFFER means that the offer is a gift card. EXCLUSIVE\_RATE\_OFFER means that the offer is a special rate for gift cards. CARD\_LINKED\_OFFER means that the offer is a special rate for virtual cards. # PayPalAccountStatus Source: https://docs.fluz.app/api-reference/types/pay-pal-account-status PayPalAccountStatus represents the current status of a PayPal account. **Enum** PayPalAccountStatus represents the current status of a PayPal account. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # PaycheckDepositType Source: https://docs.fluz.app/api-reference/types/paycheck-deposit-type Distribution method for a paycheck direct-deposit form. **Enum** Distribution method for a paycheck direct-deposit form. ## Values Deposit the entire paycheck. Deposit a fixed dollar amount. Deposit a percentage of the paycheck. Requires depositPercentage. # PaymentMethodType Source: https://docs.fluz.app/api-reference/types/payment-method-type PaymentMethodType represents the type of payment method. **Enum** PaymentMethodType represents the type of payment method. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidDeviceOs Source: https://docs.fluz.app/api-reference/types/plaid-device-os PlaidDeviceOs enables Plaid OAuth redirects for native Link SDK integrations. **Enum** PlaidDeviceOs enables Plaid OAuth redirects for native Link SDK integrations. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # PlaidLinkMode Source: https://docs.fluz.app/api-reference/types/plaid-link-mode PlaidLinkMode identifies whether the link token starts a new link or repairs an existing Plaid connection. **Enum** PlaidLinkMode identifies whether the link token starts a new link or repairs an existing Plaid connection. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # ProvisioningPlatform Source: https://docs.fluz.app/api-reference/types/provisioning-platform Target platform hint for the returned provisioning URL. **Enum** Target platform hint for the returned provisioning URL. Affects only the desktop / unknown-user-agent fallback that Branch.io routes to; actual iOS and Android opens are routed correctly regardless of this value, because the shortened link carries platform-aware redirects. ## Values End-user is expected to open the URL on iOS. Fallback resolves to the Fluz App Clip launcher (default). End-user is expected to open the URL on Android. Fallback resolves to the Fluz Android app deep link. End-user platform is unknown or web/desktop. Fallback resolves to the Fluz web app. # PurchaseScopeFilter Source: https://docs.fluz.app/api-reference/types/purchase-scope-filter PurchaseScopeFilter indicates the type of filter to use for getUserPurchases query - TOKEN_USER: use the userId from the token - TOKEN_ACCOUNT: use the accountId from the token Presence of both filters means both userId and accountId will be used. **Enum** PurchaseScopeFilter indicates the type of filter to use for getUserPurchases query * TOKEN\_USER: use the userId from the token * TOKEN\_ACCOUNT: use the accountId from the token Presence of both filters means both userId and accountId will be used. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # ScopeType Source: https://docs.fluz.app/api-reference/types/scope-type Enum describing the various types of access scopes available within the system. **Enum** Enum describing the various types of access scopes available within the system. These scopes define the extent of access granted to user tokens for specific operations. ## Values Allows access to user's payment methods and account balance. Allows access to user's purchase history. Allows access to offers catalog and inventory data. Allows making a deposit to the user's balance. Allows making an internal transfer between user's balances. Allows making a withdrawal from the user's balance. Allows purchasing a gift card. Allows revealing a gift card code. Allows handling payment card information. This scope is granted to a PCI compliant developer and all of their applications. You cannot request this when generating a token. PERSONAL/Private applications are exempt from PCI\_COMPLIANCE requirement. Allows making changes to user's payment methods. Allows revealing a virtual card details. Allows access to create virtual card. Allows access to edit virtual card. Allows application to request user KYC verification. Allows sending payout transfers from user's account to other Fluz accounts. Required when account is the SOURCE in a transfer. \[DEPRECATED] Previously used for receiving payout transfers. No longer enforced - users do not need authorization to receive money. Kept for backward compatibility with existing tokens. Allows querying a recipient. Allows to register a new business. Allows access to create a link to share an object. Allows managing authorized users (add/remove) on an account. Allows read-only access to authorized users on an account. Allows read-only access to approval requests on an account. Allows approving or declining approval requests on an account. Allows requesting manager approval to create a virtual card. Allows requesting manager approval to change a virtual card limit. Allows requesting manager approval to purchase a gift card. Allows requesting manager approval for an internal transfer. Allows requesting manager approval for an account-to-account transfer. Allows requesting manager approval for a reimbursement. Allows read-only access to ERP (e.g. QuickBooks) chart of accounts, vendors, customers, and transaction categorization state on an account. Allows managing ERP (e.g. QuickBooks) transaction categorization on an account. # ShareCardType Source: https://docs.fluz.app/api-reference/types/share-card-type ShareCardType represents the type of object you want to share. **Enum** ShareCardType represents the type of object you want to share. ## Values *No description provided in the schema yet.* # ShareLinkRecipientRegistrationStatus Source: https://docs.fluz.app/api-reference/types/share-link-recipient-registration-status **Enum** *No description provided in the schema yet.* ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ShareMethodType Source: https://docs.fluz.app/api-reference/types/share-method-type ShareMethodType represents the type of share method. **Enum** ShareMethodType represents the type of share method. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ShareObjectStatus Source: https://docs.fluz.app/api-reference/types/share-object-status ShareObjectStatus represents the status of the shared object. **Enum** ShareObjectStatus represents the status of the shared object. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # ShareObjectType Source: https://docs.fluz.app/api-reference/types/share-object-type ShareObjectType represents the type of object you want to share. **Enum** ShareObjectType represents the type of object you want to share. ## Values *No description provided in the schema yet.* # SortOrder Source: https://docs.fluz.app/api-reference/types/sort-order **Enum** *No description provided in the schema yet.* ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # SourcePlatformChannel Source: https://docs.fluz.app/api-reference/types/source-platform-channel Source channel used in approval request payloads. **Enum** Source channel used in approval request payloads. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # StockInfoType Source: https://docs.fluz.app/api-reference/types/stock-info-type StockInfoType describes the stock for either fixed or variable offers. **Union** StockInfoType describes the stock for either fixed or variable offers. Used by the Offer type. ## Possible types * [`StockInfoFixedType`](/api-reference/types/stock-info-fixed-type) * [`StockInfoVariableType`](/api-reference/types/stock-info-variable-type) # TransactionStatus Source: https://docs.fluz.app/api-reference/types/transaction-status TransactionStatus represents the current state of a transaction. **Enum** TransactionStatus represents the current state of a transaction. ## Values Transaction is pending and not yet settled. Transaction has been completed and settled. # UACRoleStatusType Source: https://docs.fluz.app/api-reference/types/uacrole-status-type Enum describing the status of a UAC role assignment. **Enum** Enum describing the status of a UAC role assignment. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # UACRoleType Source: https://docs.fluz.app/api-reference/types/uacrole-type Enum describing the UAC roles that can be assigned to authorized users. **Enum** Enum describing the UAC roles that can be assigned to authorized users. OWNER is excluded - it cannot be assigned or removed via this API. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # UserCashBalanceStatus Source: https://docs.fluz.app/api-reference/types/user-cash-balance-status Enum representing the status of a user cash balance account. **Enum** Enum representing the status of a user cash balance account. ## Values The cash balance account is active and can be used. The cash balance account has been closed. The cash balance account is temporarily suspended. # UserVerificationStatus Source: https://docs.fluz.app/api-reference/types/user-verification-status Enum describing the user verification status. **Enum** Enum describing the user verification status. ## Values User verification is approved. User verification is declined. User verification is a duplicate. User verification error. # UUID Source: https://docs.fluz.app/api-reference/types/uuid A version 4 UUID is randomly generated. **Scalar** A version 4 UUID is randomly generated. 4 bits are used to indicate version 4, and 2 or 3 bits to indicate the variant (102 or 1102 for variants 1 and 2 respectively) Custom scalar. # VirtualAccountNumberDebitControlMode Source: https://docs.fluz.app/api-reference/types/virtual-account-number-debit-control-mode Controls which debits are permitted against a virtual account number. **Enum** Controls which debits are permitted against a virtual account number. ## Values Allow all debits. Block all debits. Only allow debits from whitelisted originators. Debits require explicit approval before they are posted. # VirtualAccountNumberStatus Source: https://docs.fluz.app/api-reference/types/virtual-account-number-status Status of a virtual account number. **Enum** Status of a virtual account number. ## Values The virtual account number is active and can receive deposits. The virtual account number has been closed and can no longer be used. # VirtualCardBulkOrderStatus Source: https://docs.fluz.app/api-reference/types/virtual-card-bulk-order-status VirtualCardBulkOrderStatus represents the current state of a bulk virtual card order status. **Enum** VirtualCardBulkOrderStatus represents the current state of a bulk virtual card order status. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # VirtualCardFundingSource Source: https://docs.fluz.app/api-reference/types/virtual-card-funding-source VirtualCardFundingSource represents the funding sources available for Virtual Cards. **Enum** VirtualCardFundingSource represents the funding sources available for Virtual Cards. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # VirtualCardNetwork Source: https://docs.fluz.app/api-reference/types/virtual-card-network VirtualCardNetwork represents the card network of a virtual card (MASTERCARD or VISA). **Enum** VirtualCardNetwork represents the card network of a virtual card (MASTERCARD or VISA). ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # VirtualCardOfferType Source: https://docs.fluz.app/api-reference/types/virtual-card-offer-type VirtualCardOfferType represents the card type of a virtual card (DEBIT or PREPAID). **Enum** VirtualCardOfferType represents the card type of a virtual card (DEBIT or PREPAID). ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* # VirtualCardSpendLimitDuration Source: https://docs.fluz.app/api-reference/types/virtual-card-spend-limit-duration VirtualCardSpendLimitDuration represents the card limit duration type. **Enum** VirtualCardSpendLimitDuration represents the card limit duration type. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # VirtualCardStatus Source: https://docs.fluz.app/api-reference/types/virtual-card-status VirtualCardStatus represents the current state of a virtual card. **Enum** VirtualCardStatus represents the current state of a virtual card. ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # VirtualCardTransactionListType Source: https://docs.fluz.app/api-reference/types/virtual-card-transaction-list-type **Enum** *No description provided in the schema yet.* ## Values *No description provided in the schema yet.* *No description provided in the schema yet.* *No description provided in the schema yet.* # VirtualCardType Source: https://docs.fluz.app/api-reference/types/virtual-card-type VirtualCardType indicates whether the card is multi-use or single-use, defining its usability. **Enum** VirtualCardType indicates whether the card is multi-use or single-use, defining its usability. ## Values MULTI\_USE is a Virtual Card that can be stored and reused. SINGLE\_USE is a Virtual Card that can be used once. SINGLE\_LOAD is a Virtual Card that can be loaded once, and reused until the card limit is reached. # WithdrawMethods Source: https://docs.fluz.app/api-reference/types/withdraw-methods Enum representing different withdrawal methods available to users. **Enum** Enum representing different withdrawal methods available to users. ## Values Withdraw to a PayPal account. Withdraw via ACH transfer to a bank account. Withdraw via push-to-card to a debit card. The card must be OCT-eligible (most Visa/Mastercard debit cards). Standard withdrawals settle after a delay; set isExpedited to true for instant delivery. Withdraw to a Venmo account. Withdraw to a Fluz internal balance. # WithdrawSource Source: https://docs.fluz.app/api-reference/types/withdraw-source Enum representing the source balance type for withdrawals. **Enum** Enum representing the source balance type for withdrawals. ## Values Withdraw from the user's cash balance. Withdraw from the user's rewards balance.