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

# Your First Gift Card Purchase

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

<Info>
  **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).
</Info>

## Before you begin

Except for minting your token (Step 2), every call is a `POST` request to a single GraphQL endpoint, authenticated with your bearer token:

<CodeGroup>
  ```bash Endpoint & headers theme={null}
  POST https://transactional-graph.staging.fluzapp.com/api/v1/graphql

  Authorization: Bearer <YOUR_USER_ACCESS_TOKEN>
  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 <YOUR_USER_ACCESS_TOKEN>" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "query getWallet { getWallet { bankCards { bankCardId lastFourDigits } } }"
    }'
  ```
</CodeGroup>

<Tip>
  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-bank-cards) list.
</Tip>

## The full flow

<Steps>
  <Step title="Sign up and register an app" icon="user-plus">
    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 `clientId` and `clientSecret` — the secret is shown only once.

    Full walkthrough: [Prepare your accounts](/get-started/prepare-accounts).
  </Step>

  <Step title="Exchange credentials for an access token" icon="key">
    Mint a short-lived, scoped user access token from your app credentials. This call goes to the OAuth service; every later call uses the returned token as a Bearer credential.

    <CodeGroup>
      ```bash Mint token (cURL) theme={null}
      curl -X POST https://oauth-service.fluzapp.com/graphql \
        -H "Content-Type: application/json" \
        -d '{
          "query": "mutation ($clientId: String!, $clientSecret: String!, $scopes: [Scope!]!) { generateUserAccessToken(clientId: $clientId, clientSecret: $clientSecret, scopes: $scopes) { accessToken expiresIn scopes } }",
          "variables": {
            "clientId": "<APPLICATION_CLIENT_ID>",
            "clientSecret": "<APPLICATION_CLIENT_SECRET>",
            "scopes": ["LIST_OFFERS", "LIST_PAYMENT", "MANAGE_PAYMENT", "PURCHASE_GIFTCARD", "REVEAL_GIFTCARD"]
          }
        }'
      ```

      ```json Response theme={null}
      {
        "data": {
          "generateUserAccessToken": {
            "accessToken": "eyJhbGciOi...",
            "expiresIn": 3600,
            "scopes": ["LIST_OFFERS", "LIST_PAYMENT", "MANAGE_PAYMENT", "PURCHASE_GIFTCARD", "REVEAL_GIFTCARD"]
          }
        }
      }
      ```
    </CodeGroup>

    Store the returned `accessToken` and send it as `Authorization: Bearer <accessToken>` on every request below. Tokens are short-lived (`expiresIn` is in seconds) — mint them server-side and refresh before expiry. Full details: [API credentials](/get-started/api-credentials).

    <Tip>
      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.
    </Tip>

    <Warning>
      Never expose `clientSecret` in a browser or mobile client. Mint tokens server-side and forward only the token.
    </Warning>
  </Step>

  <Step title="Deposit funds to your Fluz balance" icon="wallet">
    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).

    <Accordion title="First, retrieve a payment method ID (getWallet)" icon="id-card">
      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 <YOUR_USER_ACCESS_TOKEN>" \
        -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).
    </Accordion>

    ### Make the deposit

    Use the `depositCashBalance` mutation. It takes a `DepositCashBalanceInput` object.

    <CodeGroup>
      ```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": "0284be6f-1a69-44f7-9da0-5b5edaf45d19"
        }
      }
      ```
    </CodeGroup>

    #### Input fields

    <ParamField body="idempotencyKey" type="string" required>
      A unique client-generated UUID that guarantees the deposit is processed only once.
    </ParamField>

    <ParamField body="amount" type="Float" required>
      The amount to deposit.
    </ParamField>

    <ParamField body="depositType" type="CashBalanceDepositType">
      Destination balance. One of `CASH_BALANCE`, `GIFT_CARD_BALANCE`, or `RESERVE_BALANCE`.
    </ParamField>

    <ParamField body="Funding source" type="UUID">
      Where the money comes from — provide **one** of `bankAccountId`, `bankCardId`, or `paypalVaultId`.
    </ParamField>

    <ParamField body="merchantCategoryCode" type="Int">
      Only for `GIFT_CARD_BALANCE`. A four-digit MCC classifying the business. Use `getMccList` to retrieve valid values.
    </ParamField>

    <ParamField body="userCashBalanceId" type="UUID">
      When `CASH_BALANCE` is selected, the specific spend account to deposit into.
    </ParamField>

    <Accordion title="Sample response" icon="code">
      ```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"
              }
            }
          }
        }
      }
      ```
    </Accordion>

    <Note>
      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.
    </Note>
  </Step>

  <Step title="Browse merchants and pick an offer" icon="store">
    With a balance ready, fetch the catalog of available merchants and their cashback offers using `getMerchants`.

    <CodeGroup>
      ```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 }
      }
      ```
    </CodeGroup>

    <Tip>
      The catalog is sorted by cashback percentage by default, so the highest offers appear first.
    </Tip>

    #### Useful arguments

    <ParamField query="name" type="String">
      Filter merchants by name.
    </ParamField>

    <ParamField query="paginate" type="OffsetInput">
      `{ limit, offset }`. Default and max `limit` is `20`.
    </ParamField>

    <ParamField query="offerTypes" type="OfferTypesInput">
      Boolean flags for which offer types to return, e.g. `{ giftCardOffer: true, cardLinkedOffer: false }`.
    </ParamField>

    <ParamField query="filterBy" type="FilterByInput">
      Filter offers within each merchant, e.g. by `deliveryFormat` (`URL`, `CODES`, `PIN_AS_CODE`, `PIN_WITH_URL`).
    </ParamField>

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

    <Accordion title="Optional: get the single best offer for a merchant (getOfferQuote)" icon="badge-percent">
      If you already know the merchant and amount, `getOfferQuote` returns the top available offer directly — including live stock info.

      <CodeGroup>
        ```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": {
            "slug": "starbucks",
            "denomination": 50.00,
            "paymentMethod": "FLUZPAY"
          }
        }
        ```
      </CodeGroup>

      `slug` and `denomination` are required. `paymentMethod` defaults to `FLUZPAY` (your Fluz balance) and also accepts `BANK_CARD`, `BANK_ACCOUNT`, `PAYPAL`, `APPLE_PAY`, and `GOOGLE_PAY`.
    </Accordion>

    <Warning>
      Cashback rates change regularly. Always confirm the current rate before purchasing.
    </Warning>
  </Step>

  <Step title="Purchase a gift card" icon="credit-card">
    Use the `purchaseGiftCard` mutation. You can identify what to buy in two ways:

    <Tabs>
      <Tab title="Option A — Merchant slug (recommended)">
        Pass a `merchantSlug` and Fluz automatically applies the best available offer for that merchant.

        <CodeGroup>
          ```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": "starbucks",
              "amount": 50.00,
              "balanceAmount": 50.00
            }
          }
          ```
        </CodeGroup>
      </Tab>

      <Tab title="Option B — Specific offer ID">
        Pass an `offerId` you selected from the catalog to purchase that exact offer.

        <CodeGroup>
          ```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
            }
          }
          ```
        </CodeGroup>
      </Tab>
    </Tabs>

    #### Input fields

    <ParamField body="idempotencyKey" type="string" required>
      A unique client-generated UUID so the purchase is processed only once.
    </ParamField>

    <ParamField body="offerId / merchantSlug" type="UUID / String" required>
      Provide **one**. `merchantSlug` auto-selects the best rate; `offerId` targets a specific offer.
    </ParamField>

    <ParamField body="amount" type="Float" required>
      The gift card amount to purchase.
    </ParamField>

    <ParamField body="Funding source" type="UUID / Float">
      How to pay. Use `balanceAmount` (Fluz balance), `bankAccountId`, `bankCardId`, or `paypalVaultId`. You may combine your Fluz balance with another source.
    </ParamField>

    <ParamField body="defaultToBalance" default="true" type="Boolean">
      Falls back to your Fluz balance if another payment method fails. Set to `false` to disable that fallback.
    </ParamField>

    <ParamField body="minRewardRate" type="Float">
      Minimum reward rate to accept when purchasing via `merchantSlug`.
    </ParamField>

    <ParamField body="exclusiveRateId" type="UUID">
      Forces a specific exclusive rate. Found in `getMerchants` for offers of type `EXCLUSIVE_RATE_OFFER`.
    </ParamField>

    <ParamField body="userCashBalanceId" type="UUID">
      The spend account (cash balance) to charge.
    </ParamField>

    <ParamField body="memo / transactionCategory / attachmentId" type="String / String / UUID">
      Optional expense metadata. `memo` max 255 chars; categories are created on first use. See [Add Expense Details](/features/add-expense-details).
    </ParamField>

    <Accordion title="Sample response" icon="code">
      ```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..."
            }
          }
        }
      }
      ```
    </Accordion>

    <Info>
      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).
    </Info>
  </Step>

  <Step title="Reveal the gift card details" icon="eye">
    Finally, retrieve the redeemable details (code, PIN, and/or URL).

    <Accordion title="Don't have the giftCardId? List your cards first (getGiftCards)" icon="list">
      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`.
    </Accordion>

    ### Reveal redemption details

    Call `revealGiftCardByGiftCardId` with the `giftCardId`.

    <CodeGroup>
      ```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"
      }
      ```
    </CodeGroup>

    <Accordion title="Sample response" icon="code">
      ```json theme={null}
      {
        "data": {
          "revealGiftCardByGiftCardId": {
            "code": "9877890000000000",
            "pin": "2014",
            "url": null,
            "termsAndConditions": "Except as required by law, Gift Cards cannot be transferred for..."
          }
        }
      }
      ```
    </Accordion>

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

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

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

<CardGroup cols={2}>
  <Card title="Virtual Cards" icon="credit-card" href="/features/virtual-cards">
    Issue and manage network-accepted virtual cards.
  </Card>

  <Card title="Wallets & Transfers" icon="wallet" href="/features/create-spend-accounts">
    Open spend accounts and move funds between them.
  </Card>

  <Card title="Transaction Activity" icon="receipt" href="/features/get-all-transactions">
    Pull, filter, and annotate transaction history.
  </Card>

  <Card title="Embedded Widgets" icon="book-copy" href="/developers/widgets">
    Drop Fluz flows straight into your own UI.
  </Card>
</CardGroup>

<Note>
  **Want to learn more?** Contact us at [support@fluz.app](mailto:support@fluz.app) to speak with our experts or request a demo.
</Note>
