Transactions
Retrieve transaction and payment data
Overview
The Transactions endpoints allow you to retrieve transaction and payment data. Transactions represent payment events that occur when agents make payments using payment intents. Each transaction includes details such as amount, merchant, date, status, and associated payment intent information. Transactions are read-only - they are automatically created when payments are processed.
Authorization: Bearer {access_token} header and the x-appid: {clientId} header in your requests.
Get Transactions
Retrieves a list of transactions based on filter criteria. You can filter, sort, and paginate the results using query parameters.
Query Parameters
Use the filter query parameter to filter, sort, and paginate results. The filter should be a JSON object passed as a URL-encoded string.
| Parameter | Type | Description |
|---|---|---|
filter |
object (JSON) | Filter, sorting, and pagination options. See filter options below. |
Filter Options
The filter object supports the following properties:
| Property | Type | Description |
|---|---|---|
where |
object | Filter conditions. See where clause examples below. |
fields |
array | Array of field names to include in the response |
order |
string | Sort order (e.g., "date DESC" or "amount ASC") |
limit |
number | Maximum number of results to return |
skip |
number | Number of results to skip (for pagination) |
include |
array | Related models to include (e.g., ["receipts", "transaction"]) |
Where Clause Examples
Common filter conditions for the where clause:
| Field | Example | Description |
|---|---|---|
ownerId |
{"ownerId": "user-id-123"} |
Filter by user who owns the transaction |
companyId |
{"companyId": "company-id-123"} |
Filter by company ID |
status |
{"status": "posted"} |
Filter by status: "posted", "pending", "declined", "canceled" |
status (multiple) |
{"status": {"inq": ["posted", "pending"]}} |
Filter by multiple status values |
date |
{"date": {"gte": "2024-01-01"}} |
Filter by date (greater than or equal) |
date (range) |
{"date": {"between": ["2024-01-01", "2024-12-31"]}} |
Filter by date range |
category |
{"category": "Travel"} |
Filter by transaction category/type |
allocationId |
{"allocationId": "allocation-id-123"} |
Filter by payment intent (allocation) ID |
merchantName |
{"merchantName": {"like": "%Starbucks%"}} |
Search by merchant name (case-insensitive partial match) |
Example Requests
Get all posted transactions for a user in a date range:
Using JavaScript (fetch):
const filter = {
where: {
ownerId: 'user-id-123',
status: 'posted',
date: {
between: ['2024-01-01', '2024-12-31']
}
},
order: 'date DESC',
limit: 50
};
const filterParam = encodeURIComponent(JSON.stringify(filter));
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Expenses?filter=${filterParam}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId
}
});
const transactions = await response.json();
Get transactions with receipts included:
Using JavaScript (fetch):
const filter = {
where: {
ownerId: 'user-id-123',
status: {inq: ['posted', 'pending']}
},
include: ['receipts'],
order: 'date DESC',
limit: 100
};
const filterParam = encodeURIComponent(JSON.stringify(filter));
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Expenses?filter=${filterParam}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId
}
});
const transactions = await response.json();
Get transactions by category and amount:
Using JavaScript (fetch):
const filter = {
where: {
ownerId: 'user-id-123',
category: 'Travel',
amount: {gte: 100}
},
order: 'amount DESC'
};
const filterParam = encodeURIComponent(JSON.stringify(filter));
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Expenses?filter=${filterParam}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId
}
});
const transactions = await response.json();
Response
Success Response (200 OK)
[
{
"id": "transaction-id-123",
"ownerId": "user-id-123",
"companyId": "company-id-123",
"status": "posted",
"date": "2024-01-15T10:30:00.000Z",
"amount": 45.50,
"currency": "USD",
"amountInLocalCurrency": 45.50,
"settlementAmount": 45.50,
"settlementCurrency": "USD",
"merchantName": "Starbucks",
"category": "Meals",
"allocationId": "allocation-id-456",
"purpose": "Coffee meeting",
"receiptRequired": true,
"isSplit": false,
"created": "2024-01-15T10:30:00.000Z",
"modified": "2024-01-15T10:30:00.000Z"
},
{
"id": "transaction-id-789",
"ownerId": "user-id-123",
"companyId": "company-id-123",
"status": "pending",
"date": "2024-01-16T14:20:00.000Z",
"amount": 250.00,
"currency": "USD",
"amountInLocalCurrency": 250.00,
"settlementAmount": 250.00,
"settlementCurrency": "USD",
"merchantName": "United Airlines",
"category": "Travel",
"allocationId": "allocation-id-789",
"purpose": "Business trip",
"receiptRequired": true,
"isSplit": false,
"created": "2024-01-16T14:20:00.000Z",
"modified": "2024-01-16T14:20:00.000Z"
}
]
Get Transaction by ID
Retrieves detailed information about a specific transaction by its ID.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
transactionId |
string | Yes | The unique identifier of the transaction |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
filter |
object (JSON) | Optional filter to specify which fields to include or related models (e.g., {"fields": ["id", "amount", "status", "date"], "include": ["receipts"]}) |
Example Request
Using cURL:
curl -X GET "https://sandbox.custodia-tech.com/md/api/Expenses/transaction-id-123?filter=%7B%22include%22%3A%5B%22receipts%22%5D%7D" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID"
Using JavaScript (fetch):
const transactionId = 'transaction-id-123';
const filter = {
include: ['receipts', 'transaction']
};
const filterParam = encodeURIComponent(JSON.stringify(filter));
const response = await fetch(`https://sandbox.custodia-tech.com/md/api/Expenses/${transactionId}?filter=${filterParam}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId
}
});
const transaction = await response.json();
Response
Success Response (200 OK)
{
"id": "transaction-id-123",
"ownerId": "user-id-123",
"companyId": "company-id-123",
"status": "posted",
"date": "2024-01-15T10:30:00.000Z",
"amount": 45.50,
"currency": "USD",
"amountInLocalCurrency": 45.50,
"settlementAmount": 45.50,
"settlementCurrency": "USD",
"merchantName": "Starbucks",
"merchantCategory": "Restaurants",
"category": "Meals",
"allocationId": "allocation-id-456",
"budgetAllocationId": "budget-allocation-id",
"purpose": "Coffee meeting",
"receiptRequired": true,
"isSplit": false,
"costCenterId": "cost-center-id",
"glAccountCode": "6001",
"receipts": [
{
"id": "receipt-id-123",
"expenseId": "transaction-id-123",
"mimeType": "image/jpeg",
"url": "https://storage.example.com/receipts/receipt-id-123.jpg"
}
],
"created": "2024-01-15T10:30:00.000Z",
"modified": "2024-01-15T10:30:00.000Z"
}
Transaction Simulation
Simulates a card authorization in sandbox. Use this to test whether a card, merchant, amount, and activity rules produce the expected authorization outcome before relying on live card spend.
Sandbox only: Transaction simulation is intended for sandbox and non-production testing. Do not use this endpoint for live card activity.
Request Body
The request body contains a details object describing the transaction to simulate:
| Field | Type | Required | Description |
|---|---|---|---|
details.cardId |
string (UUID) | Yes | The sandbox card id to use for the simulated transaction. |
details.merchantId |
string (UUID) | Recommended | The merchant id to use for matching and authorization. |
details.amount |
number | Yes | The transaction amount to authorize. Must be greater than zero. |
details.postTransaction |
boolean | No | When true, post the transaction after successful authorization. |
details.refundAmount |
number or null | No | If the transaction is posted, optionally simulate a reversal for this amount. |
details.j5Ind |
string or null | No | Issuer-specific indicator used by some card providers. |
details.delay |
number | No | Delay in milliseconds between simulation stages such as authorization, posting, and reversal. |
Example Request
Using cURL:
curl -X POST https://sandbox.custodia-tech.com/md/api/TransactionSimulations/execute \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-appid: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"details": {
"cardId": "bedfff5b-4537-4c60-8541-d35fe570e149",
"merchantId": "d4b1ff86-b8a0-4f98-b6c4-9a08b0cfbf58",
"amount": 2.99,
"postTransaction": true,
"refundAmount": null,
"j5Ind": null,
"delay": 2000
}
}'
Using JavaScript (fetch):
const response = await fetch('https://sandbox.custodia-tech.com/md/api/TransactionSimulations/execute', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-appid': clientId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
details: {
cardId: 'bedfff5b-4537-4c60-8541-d35fe570e149',
merchantId: 'd4b1ff86-b8a0-4f98-b6c4-9a08b0cfbf58',
amount: 2.99,
postTransaction: true,
refundAmount: null,
j5Ind: null,
delay: 2000
}
})
});
const simulation = await response.json();
Response
On success the API returns the simulated authorization result as JSON. The exact response fields can vary by card issuer and simulation path. Example shape (ids and token values are illustrative):
Success Response (200 OK)
{
"type": "authorization",
"state": "PENDING",
"identifier": "33503",
"token": "019e65f1-1ab0-7df0-8b2f-2cf8811cfef9",
"business_token": "4f3c0916-bee8-4f68-8d3d-ad808400a351",
"acting_user_token": "3fb782c0-476c-4e7f-af36-2a2e608a80ec",
"card_token": "0998a733-639c-4522-be92-c0bf14369dbb",
"card_product_token": "c71ddf2f-292f-4502-a2e9-f8f0f38ede11",
"is_recurring": false,
"cardholder_impact": true,
"gpa": {
"currency_code": "USD",
"ledger_balance": 58139.16,
"available_balance": 0,
"credit_balance": 0,
"pending_credits": 0,
"impacted_amount": -2.99
},
"gpa_order": {
"token": "5b7e422d-2d40-43fc-b23d-92c0ab4985b9",
"amount": 2.99,
"state": "PENDING",
"response": {
"code": "0000",
"memo": "Approved or completed successfully"
},
"funding_source_token": "**********e5b3",
"currency_code": "USD"
},
"duration": 510,
"created_time": "2026-05-26T20:19:20Z",
"request_amount": 2.99,
"amount": 2.99,
"currency_code": "USD",
"approval_code": "253861",
"response": {
"code": "0000",
"memo": "Approved or completed successfully"
},
"network": "DISCOVER",
"card": {
"last_four": "3360",
"metadata": {
"our_ccid": "bedfff5b-4537-4c60-8541-d35fe570e149",
"our_uid": "e1c93ff3-d089-49f4-8b78-445ad884168e",
"our_cid": "7332ad13-d460-457d-b73f-0e1681f060fc"
}
},
"card_acceptor": {
"mid": "863332000492496",
"mcc": "5734",
"name": "ORACLE IRELAND",
"city": "St. Petersburg",
"state": "CA",
"zip": "33705",
"country": "USA"
},
"is_installment": false
}
Transaction Webhooks
Custodia can push real-time notifications to your HTTPS endpoint when card transactions are created or updated. Use webhooks to react to authorizations, declines, and status changes without polling the Transactions API.
Event Types
| Event | When it fires |
|---|---|
transaction.created |
A transaction is first recorded: including successful authorizations and first-time declines. |
transaction.updated |
An existing transaction changes: for example when an authorization clears, a decline occurs after a prior auth, or a dispute status changes. |
webhook.test |
A test delivery sent to verify your endpoint is reachable and accepting payloads. |
Delivery
- Events are delivered as
POSTrequests with a JSON body andContent-Type: application/json. - Delivery is at-least-once; retries include
attemptandredeliveryfields so you can detect duplicates. - Use the top-level
idas an idempotency key. - Return any
2xxresponse to acknowledge receipt.
Example Payload
transaction.created (declined authorization)
{
"id": "ba31a2aa-dfac-4e6b-9623-6cf2dc0f9b5d",
"eventType": "transaction.created",
"eventTimestamp": "2026-06-23T00:58:36.389Z",
"transaction": {
"id": "UL3X9mprW785qrVkG5-FA",
"companyId": "7332ad13-d460-457d-b73f-0e1681f060fc",
"ownerId": "2d9f5599-80f9-4623-8e9f-d49e490addd6",
"cardId": "fadaacdb-c08c-401e-b6ab-ceb9a1e782ec",
"status": "declined",
"source": "card",
"classification": "business",
"amount": 0,
"authorized": 0,
"credit": 0,
"failed": 7,
"currency": "USD",
"originalAmount": 7,
"originalCurrency": "USD",
"conversionRate": 1,
"merchantName": "LINKEDIN-529*9486234",
"mcc": "5968",
"merchantId": "13f05d5d-7ff4-4179-9b1a-035cabb0e079",
"timestamp": "2026-06-23T00:58:35.000Z",
"user_timestamp": "2026-06-23T00:58:35.000Z",
"cardIssuerId": "CardIssuer",
"externalId": "BmUBM0gIe12f296SNUD87",
"journalType": "authorization",
"successful": false,
"description": "activity/no-match,expense-policy/tenant"
},
"attempt": 1,
"redelivery": false
}
Webhook Payload Fields
Each delivery wraps transaction data in a standard envelope. The transaction object includes only the allowed fields below: no other transaction properties are sent.
Envelope Fields
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Unique identifier for this webhook delivery. Use as an idempotency key. |
eventType |
string | Event name: transaction.created, transaction.updated, or webhook.test. |
eventTimestamp |
string (ISO 8601) | When Custodia generated the event. |
attempt |
number | Delivery attempt number. Starts at 1 on the first try. |
redelivery |
boolean | true when this payload is a retry after a prior failed delivery. |
transaction |
object | Transaction details. Present on transaction.created and transaction.updated events. |
Transaction Fields
| Field | Type | Description |
|---|---|---|
id |
string | Unique Custodia transaction identifier. |
companyId |
string | Company that owns the transaction. |
ownerId |
string | User (agent) who owns the card and transaction. |
cardId |
string | Card used for the payment. |
status |
string | Current transaction status. Allowed values: pending, credit, declined, card verification, canceled, failed. |
source |
string | How the transaction was created. Allowed values: card, cash. |
classification |
string | Expense classification. Allowed values: business, private, personal. |
amount |
number | Posted or settled amount in currency. |
authorized |
number | Amount currently authorized (held) on the card. |
credit |
number | Credit or refund amount applied to the transaction. |
failed |
number | Amount associated with a failed or declined authorization. |
currency |
string | ISO 4217 currency code for amount, authorized, credit, and failed. |
originalAmount |
number | Transaction amount in the merchant's original currency. |
originalCurrency |
string | ISO 4217 currency code for originalAmount. |
conversionRate |
number | Exchange rate from originalCurrency to currency. |
merchantName |
string | Merchant name as reported by the card network. |
mcc |
string | Merchant category code (MCC). |
merchantId |
string | Custodia merchant identifier, when matched. |
timestamp |
string (ISO 8601) | System timestamp when the transaction was recorded. |
user_timestamp |
string (ISO 8601) | User-facing timestamp for the transaction event. |
cardIssuerId |
string | Card issuer identifier. |
externalId |
string | External processor or network reference for the transaction. |
journalType |
string | Ledger event type that produced this update: for example authorization or authorization.clearing. |
successful |
boolean | Whether the underlying authorization or processing event succeeded. |
description |
string | Comma-separated decline reason codes when the transaction was declined or failed. Omitted when not applicable. |
transaction fields are limited to the 25 properties above. Fields with no value are omitted from the payload. Poll or fetch via Get Transaction by ID if you need the full expense record, including payment intent and receipt data not included in webhooks.
Error Responses
All endpoints may return the following error responses:
| Status Code | Description | Solution |
|---|---|---|
401 Unauthorized |
Invalid or missing access token | Verify your access token is valid and included in the Authorization header |
400 Bad Request |
Invalid filter parameters | Check that your filter JSON is properly formatted and valid |
404 Not Found |
Transaction not found | Verify the transaction ID is correct and exists for your company |
403 Forbidden |
Insufficient permissions | Verify your access token has the required scopes and permissions to view transactions |