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.

Note: All endpoints require proper authentication. Make sure to include the Authorization: Bearer {access_token} header and the x-appid: {clientId} header in your requests.
Understanding Transactions: Transactions are created automatically when payments are processed through payment intents. Each transaction contains payment information including amount, currency, merchant details, payment intent (allocation) reference, status, and other relevant payment data. Transactions are read-only and cannot be directly updated through the API.

Get Transactions

GET /md/api/Expenses

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

GET /md/api/Expenses/{transactionId}

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"
}
Note: Transactions are read-only. They are automatically created when payments are processed through payment intents. You cannot modify transactions directly through the API.

Transaction Simulation

POST /md/api/TransactionSimulations/execute

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.

Setup: Webhook endpoints are configured per company by Custodia. Contact your Custodia representative to register your URL and authentication method.

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 POST requests with a JSON body and Content-Type: application/json.
  • Delivery is at-least-once; retries include attempt and redelivery fields so you can detect duplicates.
  • Use the top-level id as an idempotency key.
  • Return any 2xx response 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.
Note: Allowed 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