> ## Documentation Index
> Fetch the complete documentation index at: https://direct-api.reap.global/llms.txt
> Use this file to discover all available pages before exploring further.

# Issue cards to your team from your own system

> Create virtual cards for team members, reveal card details securely, and track spend and balances, all from your own tools.

## What you'll build

By the end of this guide, your system will be able to:

1. **See who on your team can hold a card.**
2. **Create a virtual card for a team member** with the spending limits you choose.
3. **Show the team member their card details** (number, expiry date and CVV) in a secure, Reap-hosted window, without the data ever touching your servers.
4. **Track spending**: how much each card has spent and how much credit your business has left.

This is the "when someone joins the marketing team, our HR or finance tool gives them a card with a USD 2,000 monthly limit, lets them view the card number in our app, and shows finance how much is being spent" use case. Everything happens from your own system, without logging in to the Business Account dashboard.

## How it works

```mermaid theme={"system"}
sequenceDiagram
    participant You as Your backend
    participant API as Business Account API
    participant UI as Your app (browser)
    You->>API: GET /health (is my key working?)
    You->>API: GET /team-members (who can hold a card?)
    You->>API: GET /cards/categories (how is the card labelled?)
    You->>API: POST /cards (create the card)
    API-->>You: card id
    UI->>You: "Show my card"
    You->>API: POST /cards/{cardId}/reveal
    API-->>You: accessUrl (valid 1 min)
    You-->>UI: accessUrl
    UI->>UI: Load accessUrl in an iframe
    You->>API: GET /cards/{cardId} and GET /cards/balances
    API-->>You: card spend, account credit left
```

| Step | What you do | Endpoint | Scope needed |
| - | - | - | - |
| 1 | Check your key works | `GET /health` | none |
| 2 | Find the team member | `GET /team-members` | `team_members:read` |
| 3 | Pick a category | `GET /cards/categories` | `cards:read` |
| 4 | Create the card | `POST /cards` | `cards:write` |
| 5 | Show the card details | `POST /cards/{cardId}/reveal` | `cards:read_vault` |
| 6 | Track spend and balances | `GET /cards/{cardId}`, `GET /cards/balances` | `cards:read`, `card_account_balances:read` |
| 7 | Adjust or close the card (optional) | `PUT /cards/{cardId}/spend-control`, `/freeze`, `DELETE` | `spend_controls:write`, `cards:write` |

## Key concepts

**Every card belongs to a team member.** There is no separate "cardholder" to create. Cards are issued to people already on your Business Account team whose `inviteStatus` is `approved`; anyone else fails card creation with `422 CARDHOLDER_NOT_FOUND`. Team members must be employees or contractors of your company: the Business Account is a corporate account. To give someone a card, invite them in the dashboard first; the API reads team members but can't invite or edit them. See [Team members](/concepts/team-members.md).

**Funding plans.** Each card has a `planType`:

* **`subscription`** (default): the card draws on your business's shared credit pool. Its `spendCap` is only a spending limit (the most this card can pay), not dedicated funds, so the credit stays available to everything else on the account. The usual choice for team members' everyday cards.
* **`topUp`**: the card gets its own balance in `spendCap`, for example USD 500. The funds are isolated to that card; no other card or budget can touch them. It declines once the balance is spent, until you add more.

See [Cards](/concepts/cards.md) for the full plan types, statuses and freeze rules.

**Spending limits.** A `topUp` card's `spendCap` is its total budget: setting it replaces the total, not an increment. On any card you can set `spendControl` limits (per transaction, day, week, month, year or lifetime), and each update replaces the whole object: send every limit you want to keep. For example: "USD 2,000 per month, no single payment above USD 500." Separately, `restriction.time` limits the hours of day the card works. See [Spend controls](/concepts/spend-controls.md).

**Card numbers never touch your servers.** The reveal endpoint doesn't return the card number, expiry date or CVV. It returns a short-lived `accessUrl` (valid 1 minute) to a secure, Reap-hosted page that your app shows in an iframe. That keeps card data out of your databases, logs and PCI scope: call reveal from your backend only, serve the URL in memory to an authorized user, and never log or persist it. See [Card details](/concepts/card-details.md) for the PCI DSS dos and don'ts.

**Approvals.** If your business uses Approvals, `POST /cards` returns `202` with `status: "pendingApproval"` and no card exists until an approver accepts in the dashboard. Find the approved card by the `title` you chose. See [Approvals](/concepts/approvals.md).

## Before you begin

* **Business Account API access is enabled** for your business. Ask your Reap account manager.
* **A team administrator has created an API key** in the Business Account dashboard with these scopes: `team_members:read`, `cards:read`, `cards:write`, `cards:read_vault`, `card_account_balances:read` and, if you'll change limits later, `spend_controls:write`. See [Authentication](/api-reference/authentication.md).
* **Your business has deposited funds.** Without a deposit, card creation fails with `CARD_CREATION_REQUIRES_DEPOSIT`. See [How to add funds to your Reap Account](https://support.reap.global/hc/en-us/articles/16170706183567-How-to-add-funds-to-your-Reap-Account) in the Help Center.
* **The team member has accepted their invite** and shows as approved in the dashboard. See [Managing Your Team](https://support.reap.global/hc/en-us/articles/16170798649743-Managing-Your-Team) in the Help Center.
* **You know whether your business uses Approvals.** If it does, cards created through the API wait for an approver before they're issued (see Step 4). See [How do I set up approval workflows for my team?](https://support.reap.global/hc/en-us/articles/13939633575567-How-do-I-set-up-approval-workflows-for-my-team) in the Help Center.

All examples below use this base URL and version:

```bash theme={"system"}
export REAP_API="https://api.global.direct.reap.global/2026-07-13"
export REAP_KEY="your-api-key"   # load from a secrets manager in production
```

**1. Check your key works**

```bash theme={"system"}
curl "$REAP_API/health" -H "x-api-key: $REAP_KEY"
```

A `200` means you're connected. A `401` means the key is missing or invalid. A `403` with `API_KEY_IP_NOT_ALLOWED` means the key is restricted to certain IP addresses and your server's isn't on the list.

**2. Find the team member**

List your team and find the person who needs a card. Keep their `userId`; you'll pass it as `cardholderId` in the next step.

```bash theme={"system"}
curl "$REAP_API/team-members?page=1&limit=100" -H "x-api-key: $REAP_KEY"
```

```json theme={"system"}
{
  "items": [
    {
      "userId": "user_01JZ2Q1J4Y8P7M6N5K3H2G1F0E",
      "name": "Ada Lovelace",
      "email": "ada@example.com",
      "teamGroupName": "Marketing",
      "inviteStatus": "approved"
    }
  ],
  "meta": { "totalPages": 1, "currentPage": 1, "totalItems": 1 }
}
```

* **Only members with `inviteStatus: "approved"` can hold a card.** Anyone else returns `CARDHOLDER_NOT_FOUND`.
* **Match on `email`** to link team members to your own records. Storing the `userId` next to the employee in your system means you only look it up once.
* **Results are paginated**. Keep requesting `page` until you reach `meta.totalPages`.

**3. Pick a category**

Every card needs a `category`. It labels what the card is for and shows up in reporting.

```bash theme={"system"}
curl "$REAP_API/cards/categories" -H "x-api-key: $REAP_KEY"
```

```json theme={"system"}
{ "items": [ { "id": "software", "name": "Software" }, { "id": "travel", "name": "Travel" } ] }
```

Use the `id`, not the display name.

**4. Create the card**

```bash theme={"system"}
curl -X POST "$REAP_API/cards" \
  -H "x-api-key: $REAP_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: new-card-ada-2026-09-23" \
  -d '{
    "type": "virtual",
    "title": "Ada - Marketing tools",
    "cardholderId": "user_01JZ2Q1J4Y8P7M6N5K3H2G1F0E",
    "category": "software",
    "spendControl": {
      "monthly":     { "amount": "2000.00", "currency": "USD" },
      "transaction": { "amount": "500.00",  "currency": "USD" }
    }
  }'
```

| Field | Required | What it does |
| - | - | - |
| `type` | Yes | Always `"virtual"` |
| `title` | Yes | Card nickname shown in the dashboard. Make it clear who and what it's for |
| `cardholderId` | Yes | The team member's `userId` from step 2 |
| `category` | Yes | A category `id` from step 3 |
| `planType` | No | `subscription` (default) or `topUp` |
| `spendCap` | For `topUp` | The fixed budget for a top-up card, in USD or HKD |
| `spendControl` | No | Per-transaction, daily, weekly, monthly, yearly or lifetime limits |
| `restriction.time` | No | Hours of the day the card can be used, in UTC |
| `expiresAt` | No | Expiry date, `YYYY-MM-DD` |
| `message` | No | A note stored with the card request, visible to approvers |

> [!TIP]
>
> **Why an `Idempotency-Key`?** If the request times out and you're not sure it went through, send it again with the same key. You'll get the original card back instead of a second one.

### Handle the three possible outcomes

**1. `201`: the card is ready.** Store the `id` in your system next to the team member.

```json theme={"system"}
{
  "id": "card_01JZ2Q4T4J7QF2G6Z3X9A1B5CD",
  "type": "virtual",
  "status": "active",
  "title": "Ada - Marketing tools",
  "last4": "4242",
  "cardholder": { "userId": "user_01JZ2Q1J4Y8P7M6N5K3H2G1F0E", "name": "Ada Lovelace" },
  "spendControl": {
    "limits": {
      "monthly":     { "amount": "2000.00", "currency": "USD" },
      "transaction": { "amount": "500.00",  "currency": "USD" }
    },
    "spent": { "monthly": { "amount": "0.00", "currency": "USD" } }
  }
}
```

**2. `201` with `status: "pending"`: created, still being set up.** The card exists but isn't readable yet. Store the `id`, then call the URL in the `Location` response header every few seconds until `status` is `active`.

```json theme={"system"}
{ "id": "card_01JZ2Q4T4J7QF2G6Z3X9A1B5CD", "status": "pending" }
```

**3. `202` with `status: "pendingApproval"`: waiting for an approver.** Your business uses the Approvals feature, so an approver has to accept the request in the Business Account dashboard. No card exists yet. Once approved, the card appears in `GET /cards` and you can find it by the `title` you chose.

```json theme={"system"}
{ "approvalRequestId": "approval_01JZ2Q8A6B4C3D2E1F0G9H8J7K", "status": "pendingApproval" }
```

### Creating cards for several people

To give cards to a few team members at once (for example, a new team starting on Monday) run this step in a loop. Keep each request separate and give each its own `Idempotency-Key`:

```js theme={"system"}
for (const member of newJoiners) {
  await createCard({
    cardholderId: member.userId,
    title: `${member.name} - ${member.team}`,
    idempotencyKey: `new-card-${member.userId}`,
  });
}
```

Send requests one after another rather than all at once. If you get a `429`, wait a moment and retry with the same key.

**5. Show the card details**

When the team member wants to use their card, show them the number, expiry and CVV in a secure, Reap-hosted window.

**Your backend requests a reveal link:**

```bash theme={"system"}
curl -X POST "$REAP_API/cards/card_01JZ2Q4T4J7QF2G6Z3X9A1B5CD/reveal" \
  -H "x-api-key: $REAP_KEY"
```

```json theme={"system"}
{
  "accessUrl": "https://secure.card.reap.global/api/card/show-card-info?auth=temporary-token"
}
```

**Your app shows it in an iframe:**

```html theme={"system"}
<iframe src="ACCESS_URL_FROM_YOUR_BACKEND" title="Card details"
        style="width: 400px; height: 260px; border: 0;"></iframe>
```

The window displays the card number, expiry date and CVV, with a button to copy the card number.

> [!WARNING]
>
> **Rules for the reveal link**
>
> * **Call reveal from your backend, never from the browser.** Your API key must never reach a user's device.
> * **The link expires after 1 minute.** Request it only when the user clicks "Show card", then load the iframe right away. Never pre-fetch, store or log it. If it expires before the iframe loads, call reveal again for a fresh one.
> * **Only show people their own cards.** The `cards:read_vault` scope covers every card in the business, so your app must check that the person asking owns (or is allowed to see) the card before calling reveal.

**Why it works this way:** card data goes straight from Reap's secure environment to the user's screen. Your systems never receive or store it, which keeps your security and compliance scope much smaller. Confirm PCI DSS implications with your compliance team.

**6. Track spend and balances**

**How much has this card spent?**

```bash theme={"system"}
curl "$REAP_API/cards/card_01JZ2Q4T4J7QF2G6Z3X9A1B5CD" -H "x-api-key: $REAP_KEY"
```

Look at:

* `spendControl.spent.monthly`: spent so far this month, compared with `spendControl.limits.monthly`.
* `spentAmount`: total spent on the card (for top-up cards, compare with `spendCap`).
* `status`: whether the card is active, frozen or terminated.

To list all cards or find a team member's cards, use `GET /cards` with the `name`, `email` or `title` filter.

**How much credit does the business have left?**

```bash theme={"system"}
curl "$REAP_API/cards/balances" -H "x-api-key: $REAP_KEY"
```

```json theme={"system"}
{
  "availableCredit":    { "amount": "173788.61", "currency": "USD" },
  "totalCreditLimit":   { "amount": "250000.00", "currency": "USD" },
  "allocatedCredit":    { "amount": "7270.82",   "currency": "USD" },
  "unallocatedCredit":  { "amount": "166517.79", "currency": "USD" },
  "outstandingBalance": { "amount": "76211.39",  "currency": "USD" },
  "pendingAmount":      { "amount": "0.00",      "currency": "USD" }
}
```

The fields are explained in [Card account](/concepts/card-account.md).

Balances are calculated at request time, so they're always current. Typical uses: a finance dashboard in your own tool, or an alert when `availableCredit` drops below a threshold. For a full activity history across cards, use `GET /cards/activities`.

**7. Adjust or close the card (optional)**

| Goal | Endpoint | Notes |
| - | - | - |
| Change limits | `PUT /cards/{cardId}/spend-control` | Send the new `limits`. For top-up cards, `spendCap` is the **new total**, not an increment |
| Pause the card | `PUT /cards/{cardId}/freeze` | Reverse with `/unfreeze` |
| Close the card | `DELETE /cards/{cardId}` | For example, when someone leaves. This is permanent |

## Common errors

| Response | What it means | What to do |
| - | - | - |
| `422 CARDHOLDER_NOT_FOUND` | Team member isn't approved or the ID is wrong | Check `inviteStatus` in step 2 (see [Managing Your Team](https://support.reap.global/hc/en-us/articles/16170798649743-Managing-Your-Team) in the Help Center) |
| `422 CARD_CREATION_REQUIRES_DEPOSIT` | No funds deposited | Deposit funds in the dashboard (see [How to add funds to your Reap Account](https://support.reap.global/hc/en-us/articles/16170706183567-How-to-add-funds-to-your-Reap-Account) in the Help Center) |
| `422 CURRENCY_MISMATCH` | Currencies in the request don't match | Use one currency consistently |
| `422 IDEMPOTENCY_KEY_REUSED` | Same key sent with a different request | Use a new key for each new card |
| `409 IDEMPOTENCY_KEY_IN_FLIGHT` | The first request is still processing | Wait, then retry with the same key |
| `429` | Too many requests | Wait, then retry with the same key |
| `503 CARD_CREATION_UNAVAILABLE` | Temporarily unavailable | Retry shortly with the same key |
| `401` / `403` | Key or scope problem | Check the key and its scopes in the dashboard |

Every error includes a `message` and often a `requestId`. Include the `requestId` when contacting Reap support.

## Go-live checklist

* API key stored in a secrets manager, never sent to browsers or apps
* Key has only the scopes you need
* Team member `userId` and card `id` stored in your system
* Code handles `201`, `201 pending` and `202 pendingApproval`
* Card creation sends an `Idempotency-Key`
* Reveal called from your backend, on demand, only for the card's owner (or an authorised viewer)
* Reveal links never logged or stored


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.