---
title: Straight Through Processing with Virtual Cards
---
import { RyuNotice, RyuImage, RyuBreak } from '@ramp/ryu'
import { MDXCodeBlocks } from '~/src/components/MDXCodeBlocks'
## Straight Through Processing with Virtual Cards
This guide is for **payment platforms** that need to process vendor payments on behalf of their customers using full 16-digit card numbers and CVVs. If you're building team spending management or employee budgets, see our [Cards and Funds](/developer-api/guides/cards-and-funds) guide instead.
Ramp virtual cards are optimal for fast, controlled, and auditable payments - ideal for marketplaces, travel, and automated workflows. This guide walks you through how to issue and manage virtual cards for straight-through processing with fine-grained controls and auditability.
<RyuNotice type="warning">
**PCI API Access Required:** Full card numbers and CVVs are not available through standard API responses. [Contact developer-support@ramp.com](mailto:developer-support@ramp.com) to begin the qualification process for PCI API access.
</RyuNotice>
## Why Use Straight-Through Processing?
Ramp’s virtual card rails unlock new revenue streams and streamline vendor payments for platforms that broker services between buyers and suppliers. Earn interchange, reduce reconciliation overhead, and move fast—all without becoming a payments company.
---
## Key Features
### Reconciliation
- Automatically track transactions, refunds, and adjustments using unique card identifiers (**Card ID**).
- Simplify accounting by linking payments directly to specific bookings, vendors, or users.
- Handle complex refund scenarios without manual intervention.
### Spend Control
- Set spending caps to limit card usage to a predefined budget.
- Restrict transactions to specific merchant categories or vendors.
- Enable one-time-use or multi-use cards based on your business requirements.
### Customization
- Tailor cards with expiration dates to manage refund timelines.
- Configure restrictions for specific use cases, such as merchant locks or transaction intervals.
- Integrate seamlessly into your workflows with flexible APIs.
---
## Example Use Cases
- **Travel Aggregators** – Generate single-use virtual cards for each booking to streamline reconciliation and simplify refund workflows.
- **Field-Service Networks** – Instantly issue virtual cards to authorized repair shops at the time of service to control spend and improve visibility.
- **Marketplaces (B2B, B2C)** – Provide instant supplier payments via virtual cards while capturing margin through interchange economics.
- **Procurement & AP Platforms** – Replace LOCs and ACH with invoice- or PO-linked virtual cards to increase security and streamline accounts payable.
---
## How It Works
#### 1. Card Issuance
A virtual card is generated instantly via Ramp’s API, optionally configured with:
- **Merchant restrictions** – limiting transactions to specific providers (such as airlines or hotels)
- **Spending caps** – ensuring charges do not exceed predefined thresholds
- **Expiration dates** – to auto-expire unused cards for security and refund alignment
For detailed API specifications, see the [API Reference](#api-reference) section below.
#### 2. Payment Execution
The generated card details (PAN, CVV, expiration) are securely delivered via Ramp’s Vault API. You pass these details to your vendor, travel partner, or supplier to process payment.
#### 3. Reconciliation
After the card is charged, Ramp notifies you via webhook and exposes detailed transaction metadata via API.
- **Card ID → Transaction mapping** makes it easy to automate reconciliation
- **Supports adjustments & refunds** even after the card is locked or expired
- **Webhooks are supported** for notifications when a card is used, declined, or refunded
#### 4. Settlement & Reporting
Ramp handles issuer coordination and fund movement behind the scenes. You get daily or real-time visibility into payment status without needing to manage a processor or bank relationship.
---
## FAQ
:::warning[Questions? Contact us at developer-support@ramp.com]
:::
#### Can refunds be processed on locked cards?
Yes, refunds can be applied even after the card is locked, ensuring reconciliation remains seamless.
---
#### What support is available during implementation?
Ramp offers dedicated support, including access to a sandbox environment, technical documentation, and a partnerships team to guide you through implementation.
---
## How to Get Started
If you’re an existing Ramp customer, reach out to your Account Manager for more information. Otherwise, reach out to us via [this form](https://developer.ramp.com) with a description of your use case and we'll connect you with a specialist.
---
## Sample Code
Use the following python snippets to instantiate an API client, make your first limit (virtual card), and pull its PAN/CVV.
You can issue a virtual card and retrieve sensitive card details in one single synchronous API request.
You must have the `cards:read_vault` `limits:write` (and optionally, `users:read`) scopes enabled to run the sample code.
You’ll need to update constants like credentials and user information throughout and ensure your Python environment includes the following packages:
* `python-dotenv`
* `requests`
Copy your Client ID and Client Secret into a file named .env. The format should be:
<MDXCodeBlocks title="Set up your .env file">
```python
RAMP_CLIENT_ID=foo
RAMP_CLIENT_SECRET=bar
```
</MDXCodeBlocks>
In a separate python script:
<MDXCodeBlocks title="Load configuration and secrets">
```python
from dotenv import load_dotenv
RAMP_API_HOST = 'demo-api.ramp.com'
RAMP_VAULT_API_HOST = 'demo-vault-api.ramp.com'
load_dotenv()
```
</MDXCodeBlocks>
<MDXCodeBlocks title="Create an API client">
```python
import base64
import logging
import os
import requests
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger()
class RampApiClient:
def __init__(self, client_id: str, client_secret: str):
self.base_url = f"https://{RAMP_API_HOST}/"
self.base_vault_url = f"https://{RAMP_VAULT_API_HOST}/"
self.auth_secret = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
self._load_access_token()
self.session = requests.session()
self.session.headers = {
"Accept": "application/json",
"Authorization": f"Bearer {self.access_token}",
}
def _load_access_token(self):
response = requests.post(
f"{self.base_url}developer/v1/token",
headers={
"Accept": "application/json",
"Authorization": f"Basic {self.auth_secret}",
"Content-Type": "application/x-www-form-urlencoded",
},
data={
"grant_type": "client_credentials",
"scope": "cards:read_vault limits:write users:read",
},
)
response.raise_for_status()
self.access_token = response.json()["access_token"]
def request(self, method: str, path: str, **kwargs) -> dict:
assert path and path[0] != "/" and method.lower() in ["get", "post", "put", "delete"]
base_url = self.base_url if not kwargs.pop("use_vault", False) else self.base_vault_url
response = self.session.request(method, base_url + path, **kwargs)
try:
response.raise_for_status()
logger.info(f"{response.status_code}: {method} {base_url}{path} {response.request.body}")
except requests.exceptions.HTTPError as err:
logger.info(f"{response.status_code}: {method} {base_url}{path} {response.request.body} {response.text} {response.headers}")
raise err
return response.json() if response.text else None
CLIENT = RampApiClient(client_id=os.environ["RAMP_CLIENT_ID"], client_secret=os.environ["RAMP_CLIENT_SECRET"])
# ping-test to confirm we're live
assert CLIENT.request("get", "developer/v1/limits")
```
</MDXCodeBlocks>
At this point, CLIENT is an authenticated Ramp Developer API client.
To create cards we'll use a single synchronous API request to create a limit (with an associated virtual card) and retrieve card number information.
<MDXCodeBlocks title="Identify target user">
```python
from urllib import parse
import json
# Fill in TARGET_USER_EMAIL with the user who the cards should belong to
TARGET_USER_EMAIL = 'rampy@ramp.com'
TARGET_USER_ID = CLIENT.request(
"get", f"developer/v1/users?email={parse.quote(TARGET_USER_EMAIL)}"
)["data"][0]['id']
```
</MDXCodeBlocks>
<MDXCodeBlocks title="Make a POST request to create a card and retrieve the sensitive card details">
```python
payload = {
"user_id": TARGET_USER_ID,
"display_name": "Travel Hotel Booking Purchase",
"spending_restrictions": {
"limit": {
"amount": 500000,
"currency_code": "USD",
},
"allowed_categories": [18, 20],
"interval": "TOTAL",
},
}
response = CLIENT.request("post", "developer/v1/vault/cards", json=payload, use_vault=True)
print(json.dumps(response, indent=2))
```
</MDXCodeBlocks>
---
## API Reference
### `POST vault-api.ramp.com/developer/v1/vault/cards`
#### Request Body
| Field (dot-notation) | Type | Description |
| - | - | - |
| `user_id` | `string` (UUID) | Unique identifier for the user the rule applies to. |
| `display_name` | `string` | Human-readable name that appears in UI or reports. |
| `spending_restrictions` | **object** | Container for all spend-control settings. |
| └─ `limit` | **object** | Monetary ceiling for the restriction. |
| └─ └─ `amount` | `integer` (minor units) | Maximum spend, expressed in the smallest currency unit (e.g., cents). |
| └─ └─ `currency_code` | `string` (ISO 4217) | Currency in which `amount` is denominated. |
| └─ `lock_date` | `string (iso8601 datetime)` | Date to automatically lock the card. If lock date has passed, set to a future date or to null to unlock the card. |
| └─ `transaction_amount_limit` | **object** | Max amount per transaction.
| └─ └─ `amount` | `integer` (minor units) | Maximum spend, expressed in the smallest currency unit (e.g., cents). |
| └─ └─ `currency_code` | `string` (ISO 4217) | Currency in which `amount` is denominated. |
| └─ `allowed_categories` | `array<int>` | List of [Ramp category codes](/developer-api/v1/category-codes) allowed for the limit. |
| └─ `blocked_categories` | `array<int>` | List of [Ramp category codes](/developer-api/v1/category-codes) blocked for the limit. |
| └─ `allowed_vendors` | `array<string (UUID)>` | List of merchants allowed for the limit. |
| └─ `blocked_vendors` | `array<string (UUID)>` | List of merchants blocked for the limit. |
| └─ `blocked_mcc_codes` | `array<string>` | List of blocked MCC codes. |
| └─ `interval` | `string` enum | Time window for the limit (one of `TOTAL`, `DAILY`, `MONTHLY`, `ANNUAL`, `QUARTERLY`, `TERTIARY`, `WEEKLY`, `YEARLY`). |
| `spend_program_id` | `string` (UUID) [Optional] | The id of the associated spend program. If this field is passed, `spending_restrictions` will be ignored and this limit will inherit the `spending_restrictions` of the spend program |
```json
{
"user_id": "d8135cfe-0396-4b2d-b2cf-ad809fb04731",
"display_name": "Travel Hotel Booking Purchase",
"spending_restrictions": {
"limit": {
"amount": 500000,
"currency_code": "USD"
},
"allowed_categories": [18, 20],
"interval": "TOTAL"
}
}
```
#### Response
| Field (dot-notation) | Type | Description |
| - | - | - |
| `spend_limit_id` | `string` (UUID) | Unique identifier for this spend-limit rule. |
| `created_at` | `string` (ISO 8601 timestamp) | When the rule was created (UTC). |
| `display_name` | `string` | Human-readable label shown in UI and reports. |
| `user_id` | `string` (UUID) | User to whom the spend limit applies. |
| `card` | **object** | Physical or virtual card tied to this rule. |
| └─ `id` | `string` (UUID) | Card identifier. |
| └─ `pan` | `string` | Primary Account Number (may be masked or encrypted in production). |
| └─ `cvv` | `string` | Card-verification value (usually returned only in secure contexts). |
| `restrictions` | **object** | Container for all spend-control details. |
| └─ `limit` | **object** | Monetary ceiling for the rule. |
| └─ `amount` | `integer` (minor units) | Max spend in the smallest currency unit (e.g., cents). |
| └─ `currency_code` | `string` (ISO 4217) | Currency of `amount`. |
| └─ `interval` | `string` enum | Reset window for the limit (`DAILY`, `MONTHLY`, `TOTAL`, etc.). |
| └─ `allowed_vendors` | `array<string>` | Whitelisted merchant IDs; empty means "no explicit allow-list." |
| └─ `blocked_vendors` | `array<string>` | Blacklisted merchants. |
| └─ `allowed_categories` | `array<int>` | Permitted MCC or internal category codes. |
| └─ `blocked_categories` | `array<int>` | Forbidden category codes. |
| `spend_program_id` | `string` (UUID) \| `null` | Links this rule to a broader spend program; `null` if standalone. |
Example response:
```json
{
"spend_limit_id": "d8135cfe-0396-4b2d-b2cf-ad809fb04731",
"created_at": "2024-05-12T01:37:27+00:00",
"display_name": "Travel Hotel Booking Purchase",
"user_id": "2ba219ba-5867-453f-bec2-b8d0414b7f75",
"card": {
"id": "a40a6ce8-70d4-4d06-91e1-0728ad9bbe39",
"pan": "4403900918007455",
"cvv": "449"
},
"restrictions": {
"limit": {
"amount": 50000,
"currency_code": "USD"
},
"interval": "TOTAL",
"allowed_vendors": [],
"blocked_vendors": [],
"allowed_categories": [35],
"blocked_categories": []
},
"spend_program_id": null
}
```