Vydapay API Reference
Complete reference documentation for the Vydapay REST API. Everything you need to integrate card issuing and expense management.
https://api.vydapay.com/v1
Authentication
The Vydapay API uses API keys to authenticate requests. You can view and manage your API keys in your dashboard.
Include your API key in the Authorization header using Bearer token format:
# Using cURL
curl https://api.vydapay.com/v1/cards \
-H "Authorization: Bearer sk_live_abc123def456..."
Test vs Live Keys
| Key Type | Prefix | Purpose |
|---|---|---|
| Test Secret Key | sk_test_ | Development and testing. Transactions are simulated. |
| Live Secret Key | sk_live_ | Production use. Real transactions are processed. |
| Publishable Key | pk_ | Client-side use only. Limited access. |
Errors
Vydapay uses conventional HTTP response codes. Codes in the 2xx range indicate success, 4xx indicate client errors, and 5xx indicate server errors.
| Code | Meaning | Description |
|---|---|---|
200 | OK | Request succeeded |
201 | Created | Resource successfully created |
400 | Bad Request | Invalid parameters provided |
401 | Unauthorized | Invalid or missing API key |
403 | Forbidden | Valid key but insufficient permissions |
404 | Not Found | Resource doesn't exist |
409 | Conflict | Request conflicts with current state |
422 | Unprocessable | Valid syntax but cannot be processed |
429 | Too Many Requests | Rate limit exceeded |
500 | Server Error | Something went wrong on our end |
Error Response Format
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "The spending_limit amount must be a positive integer.",
"param": "spending_limit.amount",
"doc_url": "https://docs.vydapay.com/errors/parameter_invalid"
}
}
Error Types
| Type | Description |
|---|---|
api_error | Something went wrong on our end (rare) |
authentication_error | Invalid API key or permissions issue |
invalid_request_error | Invalid parameters in the request |
rate_limit_error | Too many requests in a short time |
card_error | Card-specific error (declined, frozen, etc.) |
Handling Errors
try {
const card = await vydapay.cards.create({
type: 'virtual',
currency: 'GBP',
cardholder_id: 'ch_abc123'
});
} catch (error) {
if (error.type === 'invalid_request_error') {
console.log('Invalid parameters:', error.message);
} else if (error.type === 'authentication_error') {
console.log('Check your API key');
} else {
console.log('Unexpected error:', error.message);
}
}
Pagination
List endpoints use cursor-based pagination for efficient retrieval of large datasets.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 25 | Number of results (1-100) |
starting_after | string | — | Cursor for next page (use last item's ID) |
ending_before | string | — | Cursor for previous page |
{
"object": "list",
"data": [
{ "id": "card_xyz789", ... },
{ "id": "card_abc456", ... }
],
"has_more": true,
"url": "/v1/cards"
}
Iterating Through Pages
// Using auto-pagination (recommended)
for await (const card of vydapay.cards.list({ limit: 100 })) {
console.log(card.id);
}
// Manual pagination
let hasMore = true;
let startingAfter = null;
while (hasMore) {
const params = { limit: 100 };
if (startingAfter) params.starting_after = startingAfter;
const response = await vydapay.cards.list(params);
for (const card of response.data) {
console.log(card.id);
}
hasMore = response.has_more;
if (response.data.length > 0) {
startingAfter = response.data[response.data.length - 1].id;
}
}
Idempotency
The API supports idempotency for safely retrying requests without accidentally performing the same operation twice. This is useful when an API call is disrupted in transit.
To perform an idempotent request, provide an Idempotency-Key header:
curl -X POST https://api.vydapay.com/v1/cards \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: unique-request-id-abc123" \
-H "Content-Type: application/json" \
-d '{"type": "virtual", "currency": "GBP", "cardholder_id": "ch_abc123"}'
Rate Limits
| Environment | Limit |
|---|---|
| Test mode | 100 requests/minute |
| Live mode (Growth) | 500 requests/minute |
| Live mode (Business) | 1,000 requests/minute |
| Live mode (Enterprise) | Custom (contact sales) |
Rate limit headers are included in every response:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1701432000
Cards
Cards are the core resource. Create virtual or physical Mastercard cards, assign them to cardholders, and configure spending controls.
The Card Object
{
"id": "card_xyz789def456",
"object": "card",
"type": "virtual",
"status": "active",
"currency": "GBP",
"cardholder_id": "ch_abc123",
"last_four": "4821",
"exp_month": 12,
"exp_year": 2027,
"brand": "mastercard",
"spending_limit": {
"amount": 50000,
"interval": "monthly"
},
"spending_controls": {
"allowed_categories": ["travel", "office_supplies"],
"blocked_categories": ["gambling"],
"allowed_countries": ["GB", "IE", "FR"]
},
"metadata": {
"department": "engineering",
"cost_center": "CC-1234"
},
"created_at": "2025-12-02T10:30:00Z",
"updated_at": "2025-12-02T10:30:00Z"
}
/v1/cards
Creates a new virtual or physical card for a cardholder.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Yes | virtual or physical |
currency | string | Yes | ISO 4217 code: GBP, EUR, USD |
cardholder_id | string | Yes | ID of the cardholder |
spending_limit.amount | integer | No | Limit in smallest currency unit (pence) |
spending_limit.interval | string | No | daily, weekly, monthly, yearly, per_transaction, all_time |
spending_controls | object | No | Category, merchant, and geographic restrictions |
metadata | object | No | Up to 20 key-value pairs |
curl -X POST https://api.vydapay.com/v1/cards \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "virtual",
"currency": "GBP",
"cardholder_id": "ch_abc123def456",
"spending_limit": {
"amount": 50000,
"interval": "monthly"
},
"spending_controls": {
"allowed_categories": ["travel", "software_services"],
"blocked_categories": ["gambling", "adult_entertainment"],
"allowed_countries": ["GB", "IE", "FR", "DE"]
},
"metadata": {
"department": "engineering",
"project": "Q4-infrastructure"
}
}'
/v1/cards
Returns a paginated list of all cards.
Query Parameters
| Parameter | Description |
|---|---|
cardholder_id | Filter by cardholder |
status | active, frozen, cancelled |
type | virtual or physical |
created_after | ISO 8601 timestamp |
created_before | ISO 8601 timestamp |
/v1/cards/:id/freeze
Temporarily disables a card. All transactions will be declined until the card is unfrozen.
const card = await vydapay.cards.freeze('card_xyz789def456');
console.log(card.status); // 'frozen'
/v1/cards/:id
Updates a card's spending limits, controls, or metadata.
const card = await vydapay.cards.update('card_xyz789def456', {
spending_limit: {
amount: 100000, // Increase to £1,000
interval: 'monthly'
},
metadata: {
project: 'Q1-expansion' // Update metadata
}
});
Cardholders
A cardholder represents an individual who can be assigned cards.
/v1/cardholders
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address |
name.first_name | string | Yes | First name |
name.last_name | string | Yes | Last name |
phone | string | No | E.164 format (+44...) |
billing_address | object | For physical | Required for physical card delivery |
const cardholder = await vydapay.cardholders.create({
email: 'sarah.mitchell@company.com',
name: {
first_name: 'Sarah',
last_name: 'Mitchell'
},
phone: '+447700900123',
billing_address: {
line1: '123 Business Street',
city: 'London',
postal_code: 'EC1A 1BB',
country: 'GB'
},
metadata: {
employee_id: 'EMP-4521',
department: 'Sales'
}
});
console.log(cardholder.id); // ch_abc123def456
Transactions
Transactions represent card purchases, refunds, and other activity. Created automatically when cards are used.
The Transaction Object
{
"id": "txn_abc123xyz789",
"object": "transaction",
"amount": 4599,
"currency": "GBP",
"status": "completed",
"type": "purchase",
"card_id": "card_xyz789def456",
"cardholder_id": "ch_abc123def456",
"merchant": {
"name": "AMAZON UK",
"category": "online_shopping",
"category_code": "5411",
"city": "London",
"country": "GB"
},
"receipt": {
"id": "rcpt_def456",
"status": "matched"
},
"created_at": "2025-12-02T14:30:00Z"
}
/v1/transactions
Query Parameters
| Parameter | Description |
|---|---|
card_id | Filter by card |
cardholder_id | Filter by cardholder |
status | pending, completed, declined, refunded |
type | purchase, refund, atm_withdrawal |
min_amount | Minimum amount in pence |
max_amount | Maximum amount in pence |
created_after | ISO 8601 timestamp |
created_before | ISO 8601 timestamp |
// Get all transactions over £100 from the last 7 days
const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const transactions = await vydapay.transactions.list({
min_amount: 10000, // £100 in pence
created_after: oneWeekAgo.toISOString(),
status: 'completed',
limit: 50
});
for (const txn of transactions.data) {
console.log(`£\${txn.amount / 100} at \${txn.merchant.name}`);
}
Receipts
Upload and manage receipts. Our AI automatically matches receipts to transactions.
/v1/transactions/:id/receipt
Attach a receipt to a transaction.
const fs = require('fs');
const receipt = await vydapay.transactions.attachReceipt(
'txn_abc123xyz789',
{
file: fs.createReadStream('./receipt.pdf'),
// Or base64 encoded
// file_base64: 'data:image/png;base64,iVBOR...'
}
);
console.log(receipt.status); // 'processing' or 'matched'
Spending Controls
Configure restrictions on how and where cards can be used.
Control Types
| Control | Description | Example |
|---|---|---|
allowed_categories | Only allow specific merchant categories | ["travel", "software_services"] |
blocked_categories | Block specific merchant categories | ["gambling", "atm"] |
allowed_countries | Only allow transactions in specific countries | ["GB", "IE", "FR"] |
blocked_countries | Block transactions in specific countries | ["RU", "BY"] |
allowed_merchants | Lock card to specific merchant IDs | ["mid_amazon", "mid_github"] |
Merchant Category Codes
Common categories you can allow or block:
airlines car_rental hotels restaurants fuel
groceries office_supplies software_services advertising
gambling atm adult_entertainment
Webhooks
Receive real-time notifications about events in your account.
Available Events
| Event | Description |
|---|---|
transaction.created | New transaction authorised |
transaction.completed | Transaction settled |
transaction.declined | Transaction declined |
transaction.refunded | Transaction refunded |
card.created | New card created |
card.frozen | Card frozen |
card.unfrozen | Card unfrozen |
card.cancelled | Card cancelled |
receipt.matched | Receipt matched to transaction |
spending_limit.reached | Card hit spending limit |
Webhook Payload
{
"id": "evt_abc123",
"object": "event",
"type": "transaction.created",
"created_at": "2025-12-02T14:30:00Z",
"data": {
"object": {
"id": "txn_abc123xyz789",
"amount": 4599,
"currency": "GBP",
// ... full transaction object
}
}
}
Verifying Webhook Signatures
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expectedSig = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig)
);
}
// In your webhook handler
app.post('/webhooks/vydapay', (req, res) => {
const signature = req.headers['x-vydapay-signature'];
if (!verifyWebhook(req.rawBody, signature, webhookSecret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.rawBody);
switch (event.type) {
case 'transaction.created':
handleNewTransaction(event.data.object);
break;
case 'card.frozen':
notifyCardholderOfFreeze(event.data.object);
break;
}
res.status(200).send('OK');
});
Account
Retrieve information about your Vydapay account.
/v1/account/balance
{
"object": "balance",
"available": {
"amount": 2450000,
"currency": "GBP"
},
"pending": {
"amount": 125000,
"currency": "GBP"
}
}