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

# Open Loop Cards Overview

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

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

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

export const ensureCardStyles = () => {
  if (typeof document === 'undefined') return;
  if (document.getElementById('fjs-om-card-css')) return;
  const st = document.createElement('style');
  st.id = 'fjs-om-card-css';
  st.textContent = `
@keyframes omGlisten{0%{transform:translateX(0) skewX(-18deg)}26%{transform:translateX(900%) skewX(-18deg)}100%{transform:translateX(900%) skewX(-18deg)}}
.om-wrap{--omh:0;container-type:inline-size;display:block;margin:1.6rem 0;text-decoration:none!important;border-bottom:none!important;background-image:none!important;box-shadow:none!important}
.om-wrap:hover,.om-wrap:focus,.om-wrap:active{text-decoration:none!important;border-bottom:none!important;background-image:none!important;box-shadow:none!important;text-decoration-thickness:0!important}
.om-wrap::before,.om-wrap::after{content:none!important;display:none!important}
.om-wrap:hover,.om-wrap:focus-visible{--omh:1}
.om-wrap:focus-visible{outline:2px solid #FEC251;outline-offset:3px}
.om-card{position:relative;display:block;border:1px solid #EAEAEA;border-radius:20px;overflow:hidden;background:#F5F4F3;cursor:pointer;aspect-ratio:684/448;padding:36px 0 0;transition:box-shadow .45s cubic-bezier(.2,.8,.2,1)}
.om-glow{position:absolute;inset:0;pointer-events:none;background:radial-gradient(72cqw 72cqw at -14% 22%,rgba(142,112,72,.18),transparent 78%),radial-gradient(72cqw 72cqw at 114% 22%,rgba(142,112,72,.18),transparent 78%)}
.om-grid{position:absolute;inset:0;pointer-events:none;opacity:.13;background-image:linear-gradient(rgba(26,0,0,.5) 1px,transparent 1px),linear-gradient(90deg,rgba(26,0,0,.5) 1px,transparent 1px);background-size:3.51cqw 3.51cqw;background-position:0 3.29cqw,3.29cqw 0;mask-image:radial-gradient(34cqw 34cqw at -14% 16%,#000 32%,rgba(0,0,0,.45) 72%,transparent 100%),radial-gradient(34cqw 34cqw at 114% 16%,#000 32%,rgba(0,0,0,.45) 72%,transparent 100%);-webkit-mask-image:radial-gradient(34cqw 34cqw at -14% 16%,#000 32%,rgba(0,0,0,.45) 72%,transparent 100%),radial-gradient(34cqw 34cqw at 114% 16%,#000 32%,rgba(0,0,0,.45) 72%,transparent 100%)}
.om-text{position:relative;display:block;text-align:left;padding:0 30% 0 5.4%}
.om-title{display:block;font-family:'Fjs Greed','Greed',ui-sans-serif,sans-serif;font-weight:600;font-size:34px;line-height:1.12;letter-spacing:-.02em;color:#1A0000}
.om-sub{display:block;font-family:'Fjs Area','Area',ui-sans-serif,sans-serif;font-size:16px;line-height:1.35;font-weight:600;color:#6E6862;margin-top:9px}
.om-frame{position:relative;display:block;margin:7.2cqw auto 0;width:85.7cqw;border:1.05cqw solid #0A0A0A;border-bottom:0;border-radius:1.9cqw 1.9cqw 0 0;background:#0A0A0A;box-shadow:0 2.4cqw 5cqw -1.6cqw rgba(26,0,0,.34);transform:translateY(calc(var(--omh,0) * -9px));transition:transform .55s cubic-bezier(.2,.8,.2,1)}
.om-shot{display:block;border-radius:.95cqw .95cqw 0 0;overflow:hidden;background:#fff;aspect-ratio:3444/2081}
.om-shot img{display:block;width:100%;height:100%;object-fit:cover;object-position:top center;margin:0}
.om-img-dark{display:none}
.om--dark .om-img-light{display:none}
.om--dark .om-img-dark{display:block}
html.dark .om--system .om-img-light{display:none}
html.dark .om--system .om-img-dark{display:block}
.om-fade{position:absolute;left:0;right:0;bottom:0;height:13cqw;pointer-events:none;background:linear-gradient(to bottom,rgba(245,244,243,0) 0%,rgba(245,244,243,.7) 46%,#F5F4F3 100%)}
.om-dim{position:absolute;inset:0;pointer-events:none;background:linear-gradient(to bottom,rgba(20,20,22,.1) 0%,rgba(20,20,22,.22) 60%,rgba(20,20,22,.3) 100%);opacity:var(--omh,0);transition:opacity .45s ease}
.om-pill{position:absolute;right:3.4%;top:9px;pointer-events:none;display:inline-flex;align-items:center;justify-content:center;gap:9px;height:48px;padding:0 26px;border-radius:100px;overflow:hidden;background:#FEC251;color:#1A0000;font-family:'Fjs Greed','Greed',ui-sans-serif,sans-serif;font-weight:600;font-size:19px;letter-spacing:-.01em;transform:scale(calc(1 + .05 * var(--omh,0)));transition:transform .5s cubic-bezier(.2,.8,.2,1),box-shadow .5s ease;box-shadow:0px 0px 2.4px 0.4px #FEC25159,0 0 calc(22px * var(--omh,0)) calc(6px * var(--omh,0)) rgba(254,194,81,calc(.5 * var(--omh,0)))}
.om-pill svg{width:18px;height:18px;flex:none}
.om-glisten{position:absolute;top:0;left:-22%;width:16%;height:100%;background:linear-gradient(100deg,rgba(255,255,255,0) 0%,rgba(255,255,255,.42) 50%,rgba(255,255,255,0) 100%);filter:blur(2px);animation:omGlisten 3.6s cubic-bezier(.45,0,.55,1) infinite}
@container (max-width:659px){
  .om-card{aspect-ratio:684/588;padding-top:26px}
  .om-title{font-size:27px}
  .om-sub{font-size:13px;margin-top:6px}
  .om-text{padding:0 7% 0 5.4%}
  .om-pill{position:static;margin-top:22px;height:40px;padding:0 20px;gap:7px;font-size:16px}
  .om-pill svg{width:15px;height:15px}
  .om-frame{margin-top:32px}
}
.om--dark .om-card,html.dark .om--system .om-card{background:#221919;border-color:#2E2823}
.om--dark .om-glow,html.dark .om--system .om-glow{background:radial-gradient(72cqw 72cqw at -14% 22%,rgba(235,222,196,.24),transparent 80%),radial-gradient(72cqw 72cqw at 114% 22%,rgba(235,222,196,.24),transparent 80%)}
.om--dark .om-grid,html.dark .om--system .om-grid{opacity:.09;background-image:linear-gradient(rgba(229,223,195,.85) 1px,transparent 1px),linear-gradient(90deg,rgba(229,223,195,.85) 1px,transparent 1px)}
.om--dark .om-title,html.dark .om--system .om-title{color:#EAEAEA}
.om--dark .om-sub,html.dark .om--system .om-sub{color:#9C9391}
.om--dark .om-frame,html.dark .om--system .om-frame{box-shadow:0 2.4cqw 5cqw -1.6cqw rgba(0,0,0,.5)}
.om--dark .om-shot,html.dark .om--system .om-shot{background:#17110C}
.om--dark .om-fade,html.dark .om--system .om-fade{background:linear-gradient(to bottom,rgba(34,25,25,0) 0%,rgba(34,25,25,.7) 46%,#221919 100%)}
.om--dark .om-dim,html.dark .om--system .om-dim{background:linear-gradient(to bottom,rgba(8,5,4,.18) 0%,rgba(8,5,4,.34) 60%,rgba(8,5,4,.44) 100%)}
`;
  document.head.appendChild(st);
};

export const ensureLoader = () => new Promise((resolve, reject) => {
  if (window.FluzDemo) return resolve();
  let s = document.querySelector('script[data-fluz-loader]');
  if (!s) {
    s = document.createElement('script');
    s.src = 'https://demos.fluz.app/loader-v1.js';
    s.async = true;
    s.setAttribute('data-fluz-loader', '1');
    document.head.appendChild(s);
  }
  s.addEventListener('load', () => resolve());
  s.addEventListener('error', () => reject(new Error('loader failed')));
});

export const DemoCard = ({demo, url, theme = 'light', image, imageDark, title = 'Demo open loop cards', subtitle = 'Step-by-step walkthrough of the API and user experience.', cta = 'Launch demo', presentation = 'full', mode}) => {
  ensureCardStyles();
  if (typeof window !== 'undefined') {
    ensureLoader().catch(() => {});
    try {
      const origin = new URL(url, 'https://demos.fluz.app').origin;
      if (!document.querySelector(`link[rel="preconnect"][href="${origin}"]`)) {
        const pc = document.createElement('link');
        pc.rel = 'preconnect';
        pc.href = origin;
        pc.crossOrigin = 'anonymous';
        document.head.appendChild(pc);
      }
    } catch (e) {}
  }
  const imgLight = image || '/images/demos/demo-shot.png';
  const imgDark = imageDark || '/images/demos/demo-shot-dark.png';
  return <a className={`om-wrap${theme === 'dark' ? ' om--dark' : theme === 'system' ? ' om--system' : ''}`} href={url || '#'} target="_blank" rel="noopener" aria-label={`Launch the interactive ${title} demo`} data-fluz-demo-open={demo} data-fluz-demo-url={url} data-fluz-demo-src="docs" data-fluz-demo-presentation={presentation} data-fluz-demo-mode={mode} onClick={e => {
    if (url && window.innerWidth < 600) return;
    e.preventDefault();
    const opener = e.currentTarget;
    ensureLoader().then(() => window.FluzDemo.open(demo, {
      url,
      src: 'docs',
      presentation,
      mode,
      opener
    })).catch(() => {
      if (url) window.open(url, '_blank', 'noopener');
    });
  }}>
      <span className="om-card">
        <span className="om-glow" /><span className="om-grid" />
        <span className="om-text">
          <span className="om-title">{title}</span>
          <span className="om-sub">{subtitle}</span>
          <span className="om-pill">
            {cta}
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" aria-hidden="true">
              <path d="M14 4h6v6M20 4l-8 8M10 20H4v-6M4 20l8-8" />
            </svg>
            <span className="om-glisten" />
          </span>
        </span>
        <span className="om-frame"><span className="om-shot">
          <img className="om-img-light" src={imgLight} alt={`${title} — interactive demo`} />
          <img className="om-img-dark" src={imgDark} alt="" aria-hidden="true" />
        </span></span>
        <span className="om-fade" /><span className="om-dim" />
      </span>
    </a>;
};

<DemoCard demo="card-issuing" url="https://demos.fluz.app/card-issuing/" theme="system" />

## How it works

<Steps>
  <Step title="You generate links">
    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.
  </Step>

  <Step title="Fluz creates a share request per link">
    Each link maps to one share request (`PENDING`) and one hosted URL.
  </Step>

  <Step title="The link is delivered">
    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.
  </Step>

  <Step title="The recipient activates and claims the card">
    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.
  </Step>
</Steps>

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

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

## 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://<your-fluz-api-host>/api/v1/graphql` with an `Authorization: Bearer <access_token>` 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).                       |

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<CardGroup cols={2}>
  <Card title="Recipient experience" icon="user" href="/features/open-loop-cards/open-loop-cards-recipient-experience">
    What your recipients see when they open a hosted link, and the rules that govern their card.
  </Card>

  <Card title="Register & Send" icon="id-card" href="/features/open-loop-cards/register-and-send">
    Use `EXISTING_USER` to register a recipient and create their card up front, instead of at claim.
  </Card>

  <Card title="Create a bulk order" icon="layers" href="/features/create-bulk-order">
    Issue many cards at once for programmatic distribution.
  </Card>
</CardGroup>
