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

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

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

<Warning>
  Basic-auth (`client_id` / `client_secret`) is **not** accepted on this query — it must be a user-context bearer token.
</Warning>

## 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://api.fluz.app/api/v1/graphql \
  -H "Authorization: Bearer <user_access_token>" \
  -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

<AccordionGroup>
  <Accordion title="Does the URL contain the card number?">
    No. The URL carries a short-lived opaque lookup id only. Credentials are exchanged securely on the device once the user opens the link.
  </Accordion>

  <Accordion title="Can the same URL be reused?">
    No. It's single-use. Generate a new one each time you need to surface the flow.
  </Accordion>

  <Accordion title="What if the user is on desktop?">
    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.
  </Accordion>

  <Accordion title="Do I need to do anything different for iOS vs. Android?">
    No. The same URL works for both — Fluz routes per-device automatically.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Send cards" icon="send" href="/features/send-cards">
    Distribute virtual cards to recipients by email, SMS, or share link.
  </Card>

  <Card title="Set a virtual card PIN" icon="key-round" href="/set-virtual-card-pin">
    Set a PIN on eligible cards before in-person, PIN-prompted transactions.
  </Card>
</CardGroup>
