> ## Documentation Index
> Fetch the complete documentation index at: https://rain-sandbox-trial.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Scoped Cards

> Create spend-limited virtual cards for AI agents and purpose-specific use cases, then retrieve their encrypted details in one request.

<Warning>
  **Security best practices:**

  * Never store decrypted card details.
  * Only request full card details when absolutely necessary.
  * Always use the latest encryption libraries to maintain security.
</Warning>

With a **scoped card**, you create a virtual card limited to the spend you authorize. Set a spending amount when you create it, and the card's lifetime limit is capped at **1.2×** that amount to absorb authorization holds. This makes scoped cards a good fit for AI agents and other automated, purpose-specific spending.

Unlike standard cards, where you [create the card](/reference/cards/create-a-card-for-a-user) and then [retrieve its encrypted details](/docs/viewing-encrypted-card-details) in a separate call, the scoped endpoint lets you do both at once. Pass an encrypted `sessionid` when creating the card, and the response returns the encrypted PAN and CVC inline.

## Prerequisites

* **Scoped card creation enabled for your tenant.** Contact Rain to enable it during onboarding; the endpoint returns a `404` until it's on.
* **The matching issuance capability** for the card's `binType`: consumer scoped cards (the default) need consumer card issuance, and `"binType": "commercial"` needs corporate card issuance. Creating a card without the capability returns a `403`.
* **A user** with a linked cardholder to issue the card to.
* **A `sessionid`:** an encrypted session ID that protects the returned card details. See [generating a session ID](/docs/using-encryption-outside-of-a-browser-environment) and use the [public key for your environment](/docs/resource-sessionid-keys).

## Creating a scoped card

<Steps>
  <Step title="Generate a session ID">
    Create a 32-character hex secret and RSA-OAEP encrypt it with Rain's [public key for your environment](/docs/resource-sessionid-keys). Keep the secret, you'll need it to decrypt the response. See the full [session ID example](/docs/using-encryption-outside-of-a-browser-environment).
  </Step>

  <Step title="Create the card">
    Call [create a scoped card](/reference/cards/create-a-scoped-card-for-a-user) with the amount in USD cents and the encrypted `sessionid` header.

    ```bash theme={null}
    curl --request POST \
      --url https://api-dev.rain.xyz/v1/issuing/users/{userId}/cards/scoped \
      --header 'Api-Key: <API_KEY>' \
      --header 'sessionid: <ENCRYPTED_SESSION_ID>' \
      --header 'content-type: application/json' \
      --data '{ "amountInUSDCents": 4299 }'
    ```
  </Step>

  <Step title="Decrypt the card details">
    The response returns the card metadata plus the encrypted PAN and CVC. Decrypt `encryptedPan` and `encryptedCvc` with your session secret using AES-128-GCM, see [decrypting card details](/docs/using-encryption-outside-of-a-browser-environment).

    ```json theme={null}
    {
      "id": "b1a2c3d4-...",
      "encryptedPan": { "iv": "base64_iv", "data": "base64_data" },
      "encryptedCvc": { "iv": "base64_iv", "data": "base64_data" },
      "last4": "4242",
      "expirationMonth": 12,
      "expirationYear": 2028,
      "status": "active"
    }
    ```
  </Step>
</Steps>

<Note>
  Decryption happens client-side, so the card is created even if your decryption step fails. If that happens, use the returned `id` to retry retrieval via the [get a card's encrypted data](/reference/cards/get-a-cards-encrypted-data) endpoint.
</Note>

## Setting transaction amount

When you create a scoped card, you must set its spending limit by passing `amountInUSDCents` in the request body.

By default, Rain applies a 1.2x ceiling on top of `amountInUSDCents` to buffer for authorization holds. For example, a request of `4299` cents allows authorizations up to `5158` cents before Rain declines them. This buffer accounts for cases where the final amount exceeds the original request, such as pre-authorization holds or tip adjustments.

```bash theme={null}
curl --request POST \
  --url https://api-dev.rain.xyz/v1/issuing/users/{userId}/cards/scoped \
  --header 'Api-Key: <API_KEY>' \
  --header 'sessionid: <ENCRYPTED_SESSION_ID>' \
  --header 'content-type: application/json' \
  --data '{ "amountInUSDCents": 4299 }'
```

Card creation returns a `400` error if `amountInUSDCents`:

* Is omitted. It is a required field.
* Is not an integer, is less than `1`, or is more than `10000000` (\$100,000).

To change the buffer, pass `bufferPercentage`: an integer from `0` to `20`,
applied on top of `amountInUSDCents`. Omitting it applies the default of `20`.

## Setting an expiration

When you create a scoped card, you can set an optional expiration by passing `expiresAt` in the request body. Provide an absolute timestamp in ISO-8601 format with a UTC offset. For example, `2026-09-01T00:00:00Z` or `2026-09-01T00:00:00+00:00`.

```bash theme={null}
curl --request POST \
  --url https://api-dev.rain.xyz/v1/issuing/users/{userId}/cards/scoped \
  --header 'Api-Key: <API_KEY>' \
  --header 'sessionid: <ENCRYPTED_SESSION_ID>' \
  --header 'content-type: application/json' \
  --data '{ "amountInUSDCents": 4299, "expiresAt": "2026-09-01T00:00:00Z" }'
```

Card creation returns a `400` error if `expiresAt`:

* Omits the UTC offset. It must end in `Z` or an offset such as `+00:00`.
* Is not a valid timestamp, or is in the past.
* Is more than 365 days in the future.

After a scoped card's `expiresAt` passes, Rain declines new authorizations on that card. Rain judges expiration by when the transaction occurred, not when it receives the message, so a delayed or replayed transaction is still evaluated against the moment it took place.

Refunds and other credits back to the card are exempt: Rain never declines money owed back to the cardholder for expiration. If you create a scoped card without an `expiresAt`, it never expires this way and keeps authorizing as usual.

## Restricting merchant categories

When you create a scoped card, you can limit where it spends by passing an optional `allowedMccs` allow-list in the request body. Each entry is a four-digit **merchant category code (MCC)**. For example, `5411` for grocery stores or `5812` for restaurants.

```bash theme={null}
curl --request POST \
  --url https://api-dev.rain.xyz/v1/issuing/users/{userId}/cards/scoped \
  --header 'Api-Key: <API_KEY>' \
  --header 'sessionid: <ENCRYPTED_SESSION_ID>' \
  --header 'content-type: application/json' \
  --data '{ "amountInUSDCents": 4299, "allowedMccs": ["5411", "5812"] }'
```

Card creation returns a `400` error if `allowedMccs`:

* Is present but empty. Omit the field entirely to apply no restriction.
* Contains duplicate codes.
* Contains a code that is not a recognized four-digit MCC.

Once a scoped card has a non-empty `allowedMccs` list, Rain declines new authorizations at any merchant whose category is not on the list. An empty or omitted list applies no category restriction, so the card authorizes at any merchant.

Refunds and other credits back to the card are exempt: Rain never declines money owed back to the cardholder based on merchant category. If a transaction arrives without a readable merchant category code, Rain approves it rather than declining it.

## Restricting merchants

When you create a scoped card, you can also limit it to specific merchants by passing an optional `allowedMerchants` allow-list in the request body. Each entry is a merchant name, such as `Uber` or `Amazon`. You can combine `allowedMerchants` with `allowedMccs`; each restriction is enforced independently.

```bash theme={null}
curl --request POST \
  --url https://api-dev.rain.xyz/v1/issuing/users/{userId}/cards/scoped \
  --header 'Api-Key: <API_KEY>' \
  --header 'sessionid: <ENCRYPTED_SESSION_ID>' \
  --header 'content-type: application/json' \
  --data '{ "amountInUSDCents": 4299, "allowedMerchants": ["Uber", "Uber Eats"] }'
```

Card creation returns a `400` error if `allowedMerchants`:

* Is present but empty. Omit the field entirely to apply no restriction.
* Contains more than **25** names.
* Contains a blank entry, a name longer than **64 characters** (after trimming), or duplicate names.

Once a scoped card has a non-empty `allowedMerchants` list, Rain matches each authorization's merchant name against the list and declines non-matching merchants with the reason `merchant scope mismatch`. Matching is a best-effort name match against the merchant name on the authorization, so include the name variants you expect to see on statements (for example, both `Uber` and `Uber Eats`).

Refunds, other credits back to the card, and \$0 account verifications are exempt: they are never declined based on the merchant list. If the merchant name on a transaction is too short or garbled to match reliably, Rain approves it rather than declining it.

## Spending and creation limits

<Info>
  Default limits (configurable during onboarding):

  * Up to **10 active** scoped cards per user.
  * Up to **10 scoped cards created** per user in a rolling 24-hour window.
  * Up to **\$5,000 approved spend** across a user's scoped cards in a rolling 24-hour window.
</Info>

Card creation returns a `400` error if you exceed the active-card or creation-velocity limit. Rain enforces the daily spend limit at authorization time and declines any transaction that would exceed it with the reason `scoped_daily_spend_limit_exceeded`. Contact Rain's team to configure custom thresholds for scoped card spending and creation limits.

## What's next

<CardGroup cols={2}>
  <Card title="Card Management Best Practices" icon="shield-check" href="/docs/card-management-best-practices">
    Apply spend controls, including scoped cards, alongside spending limits and merchant blocks.
  </Card>

  <Card title="Viewing Encrypted Card Details" icon="eye" href="/docs/viewing-encrypted-card-details">
    Retrieve full card details for standard cards.
  </Card>
</CardGroup>
