> ## 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 Virtual Card Purchase

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

<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 GetVirtualCardOffers { getVirtualCardOffers { offerId programName rewardValue } }"
    }'
  ```
</CodeGroup>

<Tip>
  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-bank-cards) for the full sandbox data set.
</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": ["MANAGE_PAYMENT", "CREATE_VIRTUALCARD", "EDIT_VIRTUALCARD", "REVEAL_VIRTUALCARD", "PCI_COMPLIANCE"]
              }
            }'
      ```

      ```json Response theme={null}
          {
            "data": {
              "generateUserAccessToken": {
                "accessToken": "eyJhbGciOi...",
                "expiresIn": 3600,
                "scopes": ["MANAGE_PAYMENT", "CREATE_VIRTUALCARD", "EDIT_VIRTUALCARD", "REVEAL_VIRTUALCARD", "PCI_COMPLIANCE"]
              }
            }
          }
      ```
    </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 fund your balance, `CREATE_VIRTUALCARD` to browse programs and issue cards, `REVEAL_VIRTUALCARD` + `PCI_COMPLIANCE` to reveal cards and pull their transactions, and `EDIT_VIRTUALCARD` to lock, unlock, or edit cards later.
    </Tip>

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

  <Step title="Fund your Fluz balance" icon="wallet">
    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.

    <Accordion title="Deposit via API (depositCashBalance)" icon="banknote-arrow-down">
      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": "<YOUR_TEST_BANK_CARD_ID>"
              }
            }
      ```

      The [gift card Quickstart](/quickstart) walks through this step in full detail, including retrieving payment method IDs with `getWallet`.
    </Accordion>

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

  <Step title="Browse card programs and pick an offer" icon="layers">
    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`.

    <CodeGroup>
      ```graphql Query theme={null}
          query GetVirtualCardOffers {
            getVirtualCardOffers {
              offerId
              programName
              bin
              bankName
              rewardValue
              programLimits {
                dailyLimit
                weeklyLimit
                monthlyLimit
              }
            }
          }
      ```

      ```json Response theme={null}
          {
            "data": {
              "getVirtualCardOffers": [
                {
                  "offerId": "ed669305-5e43-40a0-9a25-7a15ed174628",
                  "programName": "Virtual Card",
                  "bin": "543210",
                  "bankName": "Bank of Examples",
                  "rewardValue": "1.5%",
                  "programLimits": {
                    "dailyLimit": "500.00",
                    "weeklyLimit": "2000.00",
                    "monthlyLimit": "5000.00"
                  }
                }
              ]
            }
          }
      ```
    </CodeGroup>

    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 |
    | -------------------------------------- | ---------------------------------------------- | ------------ |
    | `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%         |

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

  <Step title="Create the card, configured for your use case" icon="credit-card">
    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:

    <Tabs>
      <Tab title="One-off purchase">
        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": "ed669305-5e43-40a0-9a25-7a15ed174628",
                    "spendLimit": 150.00,
                    "lockCardNextUse": true,
                    "cardNickname": "Vendor payment — Acme invoice #1042"
                  }
                }
        ```
      </Tab>

      <Tab title="Subscription with a monthly cap">
        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": "ed669305-5e43-40a0-9a25-7a15ed174628",
                    "spendLimit": 29.99,
                    "spendLimitDuration": "MONTHLY",
                    "cardNickname": "SaaS — analytics subscription"
                  }
                }
        ```
      </Tab>

      <Tab title="Budgeted, time-boxed card">
        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": "ed669305-5e43-40a0-9a25-7a15ed174628",
                    "spendLimit": 2000.00,
                    "lockDate": "2026-09-30",
                    "userCashBalanceId": "<SPEND_ACCOUNT_ID>",
                    "usePrepaymentBalance": false,
                    "useRewardsBalance": false,
                    "cardNickname": "Q3 conference travel"
                  }
                }
        ```
      </Tab>
    </Tabs>

    All three run the same mutation:

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

    #### The settings, at a glance

    <ParamField body="spendLimit" type="Float" required>
      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.
    </ParamField>

    <ParamField body="spendLimitDuration" default="LIFETIME" type="VirtualCardSpendLimitDuration">
      How the limit resets. `LIFETIME` caps total spend; `DAILY` / `WEEKLY` / `MONTHLY` make it a rolling budget — the right choice for subscriptions and team allowances.
    </ParamField>

    <ParamField body="lockCardNextUse" default="false" type="Boolean">
      Locks the card after its first successful use — the "virtual single-use card" pattern for one-off vendor payments.
    </ParamField>

    <ParamField body="lockDate" default="47 months from creation" type="String">
      `yyyy-mm-dd` date the card freezes. Time-box cards to a project, trip, or contract period.
    </ParamField>

    <ParamField body="primaryFundingSource" default="FLUZ_BALANCE" type="VirtualCardFundingSource">
      Where spend is drawn from. `FLUZ_BALANCE` uses your pre-funded balance; `BANK_ACCOUNT` pulls from a linked account (requires `bankAccountId`).
    </ParamField>

    <ParamField body="usePrepaymentBalance / useRewardsBalance" default="true" type="Boolean">
      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.
    </ParamField>

    <ParamField body="memo / transactionCategory" type="String">
      Optional expense metadata attached to the resulting transaction — categories are created on first use. See [Add Expense Details](/features/add-expense-details).
    </ParamField>

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

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

  <Step title="Reveal the card details" icon="eye">
    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.

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

    Requires the `REVEAL_VIRTUALCARD` scope.

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

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

  <Step title="Spend, then watch the activity" icon="receipt">
    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.

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

    Requires the `PCI_COMPLIANCE` and `REVEAL_VIRTUALCARD` scopes. 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).

    <Accordion title="Done with the card? Lock it (lockVirtualCard)" icon="lock">
      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).
    </Accordion>
  </Step>
</Steps>

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

<CardGroup cols={2}>
  <Card title="Edit, lock & manage cards" icon="pencil" href="/features/edit-virtual-card">
    Change limits, nicknames, and lock dates on existing cards.
  </Card>

  <Card title="Digital wallets & PINs" icon="smartphone" href="/digital-wallet-push-provisioning">
    Push cards into Apple Pay / Google Wallet and set PINs.
  </Card>

  <Card title="Bulk issuance" icon="layers" href="/features/create-bulk-order">
    Create up to 10,000 cards in a single order.
  </Card>

  <Card title="Send cards to others" icon="send" href="/features/send-cards">
    Distribute cards to recipients by link, email, or SMS.
  </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>
