> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fluz.app/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

<Note>
  **"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.
</Note>

### 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`.

<Warning>
  **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.
</Warning>

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.

<Callout icon="📘">
  ### 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.
</Callout>

## 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": []
            }
          ]
        }
      }
    }
  }
}
```

<Callout icon="🚧">
  ### **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.
</Callout>

## 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)
