Sandbox API Reference
Introduction
The sandbox environment includes simulation endpoints for generating test activity: transaction scenarios against enrolled cards, and synthetic network file ingestion to drive the bulk-enrollment pipeline. No real network is ever called.
This page is the endpoint reference for those simulation endpoints. For what the environment is, how to get a token, and what is included, start at Sandbox Testing. For guided end-to-end walkthroughs, see Testing Single Card Enrollment, Testing Bank Linking and Testing Bulk Enrollment.
All endpoints below are sandbox-only, served from https://api.sandbox.astrada.co, and require an OAuth2 access token with the simulation:write scope. They all return 202 Accepted and process asynchronously: results are observed through the existing read APIs (transactions, card subscriptions) and webhooks.
Requests use the standard headers only: Authorization: Bearer <access-token> and Content-Type: application/json. Account context is derived from your access token (no Account header is needed), matching the rest of the public API.
Simulate a transaction scenario
POST /simulate/transaction/scenario
Runs a predefined transaction scenario against an enrolled card in the sandbox, publishing the resulting transaction messages to the ingestion pipeline.
Body params
| Param | Type | Required | Description |
|---|---|---|---|
scenario | string (enum) | Required | The predefined scenario to simulate. One of: simple_purchase, split_shipment, tip_adjustment, expired_auth, refund, hotel_preauth, car_rental, gas_pump, partial_clearing, recurring_subscription, fx_conversion, amex_auth_baseline, amex_auth_adjustment, amex_auth_reversal, uber_canada_reversal, uber_netherlands_positive_advice, uber_turkey_dual_auth, uber_brazil_cancelled, uber_korea_settle_at_auth, uber_turkey_fx_reversal, uber_india_fx_clearing. |
cardId | string (uuid) | Required | The identifier of the enrolled card to transact against. |
subaccountId | string (uuid) | Required | The subaccount identifier the card belongs to. |
amount | integer (≥1) | Optional | Transaction amount in minor units (cents). When omitted, the scenario's default amount is used. |
currency | string (3 chars) | Optional | ISO 4217 three-letter currency code. When omitted, the scenario's default currency is used. |
network | string (enum) | Optional | Card network override (VISA, MASTERCARD, AMEX), so one scenario can run against any network. When omitted, the scenario's own default network is used. |
source | string (enum) | Optional | Ingestion-source tag applied to every emitted message (amexgl, amexrta, smartdata, tns, vbds). When omitted, the scenario's own source if it sets one (the amex_auth_* scenarios default to amexrta), else tns, or amexgl when the resolved network is AMEX. |
The three amex_auth_* scenarios mirror Amex's sandbox RTA mock authorizations: amex_auth_baseline (a single approved auth), amex_auth_adjustment (initial auth → adjusted auth → clearing at the adjusted amount), and amex_auth_reversal (initial auth → follow-up hold → reversal → clearing of the initial). They default to the AMEX network, so you can trigger the same Amex auth lifecycles Amex publishes as mock auth IDs, on any enrolled card. Full step shapes are in sandbox-simulation docs/SYNTHETIC_TRANSACTIONS.md.
The seven uber_* scenarios are multi-country Uber ride-share authorization flows (Canada, Netherlands, Turkey, Brazil, Korea and India), covering reversals, positive advice, dual authorizations, cancellations, settle-at-auth and FX clearing. Full step shapes are in sandbox-simulation docs/SYNTHETIC_TRANSACTIONS.md.
Example request
{
"scenario": "simple_purchase",
"cardId": "24c4f90d-ab3b-4f6e-8a1d-d251a4fcf34c",
"subaccountId": "f297d659-c13d-4219-aeaa-e10a845140a5",
"amount": 1500,
"currency": "USD"
}Responses
| Status | Description |
|---|---|
202 | Accepted: the transaction messages were published to the ingestion pipeline and will be processed asynchronously. Body contains transactions[], each with the messageIds (uuids) published for that transaction; use GET /transaction-messages/{transactionMessageId} to retrieve each message once processed. |
400 | Bad request: validation error on the request body. |
401 | Unauthorized: missing or invalid access token. |
403 | Forbidden: token does not have the simulation:write scope. |
500 | Server error. |
Example 202 response
{
"transactions": [
{
"messageIds": [
"9b2e6c4a-1f3d-4e8b-9a7c-2d5f8e1b4c6a",
"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
]
}
]
}Granular transaction simulation
The scenario endpoint publishes a whole lifecycle at once. The two endpoints below publish one transaction message per call, so you can drive a transaction step by step and observe each webhook: start a transaction on a card, then add authorizations, adjustments, reversals, settlements and refunds to it in whatever order your test needs. Both return 202 with the id of the single message published; the transaction itself arrives asynchronously through the transaction.created / transaction.updated webhooks.
Both endpoints read from the public API on your behalf, so the access token needs read scopes in addition to simulation:write: cards:read for POST /simulate/transactions, and transactions:read plus transaction-messages:read for POST /simulate/transactions/{transactionId}.
Start a transaction
POST /simulate/transactions
Publishes the first message of a new transaction on an enrolled card. The subaccount and (unless overridden) the network are read from the card.
| Param | Type | Required | Description |
|---|---|---|---|
cardId | string (uuid) | Required | The enrolled card to transact against. 404 when the card is not on your account. |
type | string (enum) | Required | authorization creates a PENDING transaction with amount on hold. settlement creates a SETTLED transaction with no prior authorization; a later backdated authorization step joins it. |
amount | integer (≥1) | Optional | Amount in minor units of currency. Default 4500. |
currency | string (3 chars) | Optional | ISO 4217 code, case insensitive; fund and precious-metal codes are rejected. Default USD. |
network | string (enum) | Optional | VISA, MASTERCARD or AMEX. Default: the card's own network (400 when this is omitted and the card has none or one outside the list). |
merchant | object | Optional | name (required), mcc (required, 4 digits), city, country (ISO 3166-1 alpha-3, case insensitive, for example USA or GBR). Default: a fixed sandbox coffee-shop merchant in the US. |
dateTime | string (date-time) | Optional | ISO 8601 with offset, not in the future. Default: now. |
{
"cardId": "24c4f90d-ab3b-4f6e-8a1d-d251a4fcf34c",
"type": "authorization",
"amount": 4500
}Response 202:
{
"messageId": "9b2e6c4a-1f3d-4e8b-9a7c-2d5f8e1b4c6a"
}To get the transactionId for the next step, take id from the transaction.created webhook, or call GET /transactions?transactionMessageId=<messageId> once the pipeline has processed the message (a few seconds).
Add a message to a transaction
POST /simulate/transactions/{transactionId}
Publishes one more message on an existing transaction, reusing its card, network, currency, merchant and network reference so the pipeline links it to the same transaction (for refund, the account's refund setting then decides whether it nets in or becomes a separate refund transaction; see the table below). Works on transactions created by this endpoint or by the scenario endpoint. 404 when the transaction is not on your account; 400 for bank transactions.
| Param | Type | Required | Description |
|---|---|---|---|
type | string (enum) | Required | One of authorization, adjustment, reversal, settlement, refund; see the table below. |
amount | integer (≥1) | Per type | Minor units of the transaction's currency; see the table below. |
dateTime | string (date-time) | Optional | ISO 8601 with offset, not in the future. Default: one second after the transaction's latest message, or now if that is later (now when the latest message is already ahead of the clock, as an Amex end-of-day clearing is). Set an earlier value to backdate a message. |
type | Effect | amount | 409 when |
|---|---|---|---|
authorization | Additional authorization; increases the hold. | Required | never |
adjustment | Replaces the current hold with amount (the new total). | Required | nothing is on hold |
reversal | Releases amount from the hold. | Default: the remaining hold | nothing is on hold, or amount exceeds the hold |
settlement | Clears amount from the hold into the settled amount. | Default: the remaining hold; required when nothing is on hold | never |
refund | Negative settlement linked to this transaction. What happens next follows the account's production refund setting: while the transaction is still PENDING, or on accounts that correlate refunds, it nets into this transaction (transaction.updated); on accounts that do not (the default for accounts created through the API, including sandbox accounts) and once the transaction is SETTLED, it arrives as a separate refund transaction with a negative settled amount and its own transaction.created, and this transaction receives no webhook. | Default: the settled amount; required when nothing has been settled | never |
The 409 checks compare against the hold the pipeline has processed so far and are best effort: a step sent immediately after another may still see the earlier state. Wait for the webhook of the previous step before sending one that depends on it.
{
"type": "settlement"
}Response 202:
{
"messageId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}Example flows
Authorization then settlement. POST /simulate/transactions with { "cardId": "…", "type": "authorization", "amount": 4500 } → transaction.created, PENDING, hold 45.00. Then POST /simulate/transactions/{transactionId} with { "type": "settlement" } → transaction.updated, SETTLED, settled 45.00, hold 0. A further { "type": "reversal" } returns 409 because nothing is on hold.
Settlement first. POST /simulate/transactions with { "cardId": "…", "type": "settlement", "amount": 4500 } → transaction.created, SETTLED, hold 0. Then POST /simulate/transactions/{transactionId} with { "type": "authorization", "amount": 4500, "dateTime": "<one minute before the settlement>" } → transaction.updated, still SETTLED, hold 0, with the authorization ordered before the settlement in GET /transactions/{transactionId}.
Responses
| Status | Description |
|---|---|
202 | Accepted: one transaction message was published. Body contains its messageId (uuid). |
400 | Bad request: validation error on the request body, a card without a network, a bank transaction, or a missing required amount. |
401 | Unauthorized: missing or invalid access token. |
403 | Forbidden: token does not have the simulation:write scope. |
404 | Not found: the card or transaction does not exist on your account. |
409 | Conflict (step endpoint only): the adjustment or reversal does not fit the current hold; see the table above. |
500 | Server error. |
Simulate Visa VCF ingestion
POST /simulate/ingestion/vcf
Writes a synthetic Visa bulk-enrollment file to the ingestion bucket to drive the bulk-enrollment pipeline without a real network file delivery. The pipeline then runs asynchronously: enrolled cards appear as card subscriptions once processing completes.
Prerequisite: a bulk feed must already exist for this account, created via POST /network-bulk-feeds. The networkReference below must match that feed.
Repeat calls, new cards, and transactions
The simulated cards come from a fixed, reserved test-card pool (BIN 404507; the full list is published in Test Card Pools below), mirroring how production files carry a stable card population:
cardCountincludes the head of the pool, the same cards every call. Once they are
enrolled in the subaccount, re-ingesting them deduplicates (no newcardsubscription.created),
exactly like a re-delivered production file.newCardCountdraws cards this subaccount has never seen. Every call enrolls that many new
cards and emits theircardsubscription.createdwebhooks. Use this to re-test the full
feed → cards → transactions flow without creating a new subaccount.- Feed activation: a feed only moves from
submittedtoactiveonce an ingestion creates at
least one new card data link. On a feed that has never completed an ingestion, a head-only replay
would deduplicate and leave the feed stuck, sonewCardCount: 0still draws one never-seen
card. This happens at most once per feed; once the feed is active, replays stay pure. - Every included card (head or new) gets
transactionsPerCardtransactions with fresh
transaction references and recent dates, so repeat calls always emittransaction.created/
transaction.updatedwebhooks, including for already-enrolled cards.
Enrolled sandbox cards carry cardholder data: GET /cards/{cardId}/cardholder returns firstName, lastName, postalCode, email, and corporate.employeeId for cards enrolled through this endpoint. The exact field set varies by network. See the SmartData and Amex GL sections below for their differences (notably, email).
Body params
| Param | Type | Required | Description |
|---|---|---|---|
networkReference | string | Required | The Visa VCF PIC for the bulk feed, in the format 'ProcessorId_IssuerId_CompanyId', such as 123_45_6789. The full RPIC used at feed creation ('RegionId_ProcessorId_IssuerId_CompanyId', such as 1_123_45_6789) is also accepted; the region segment is stripped before use. Must match the feed created via POST /network-bulk-feeds. Pattern: ^\d+(_\d+){2,3}$ |
cardCount | integer (0–100) | Optional (default 5) | Cards from the head of the test-card pool, the same cards every call; deduplicated once enrolled. cardCount + newCardCount must be at least 1. |
newCardCount | integer (0–20) | Optional (default 0) | Not-yet-enrolled cards drawn from the pool for this feed's subaccount. Each call enrolls this many new cards. On a feed that has never completed an ingestion, leaving this at 0 still draws one new card so the feed can activate (see above). |
transactionsPerCard | integer (0–10) | Optional (default 3) | Transactions generated per included card, with fresh references and recent dates each call. Use 0 for a cards-only starter file. |
Example request
{
"networkReference": "123_45_6789",
"cardCount": 0,
"newCardCount": 5,
"transactionsPerCard": 3
}Responses
| Status | Description |
|---|---|
202 | Accepted: the synthetic file was written to the ingestion bucket and the pipeline will process it asynchronously. Empty response body. |
400 | Bad request: validation error on the request body; no active feed for the networkReference; or the subaccount has exhausted the new-card pool. |
401 | Unauthorized: missing or invalid access token. |
403 | Forbidden: token does not have the simulation:write scope. |
413 | Content too large: a numeric field exceeded its maximum (cardCount above 100, newCardCount above 20, or transactionsPerCard above 10). |
500 | Server error. |
Simulate Mastercard SmartData ingestion
POST /simulate/ingestion/smartdata
Writes a synthetic Mastercard bulk-enrollment file to the ingestion bucket to drive the bulk-enrollment pipeline without a real network file delivery. The pipeline then runs asynchronously: enrolled cards appear as card subscriptions once processing completes.
Prerequisite: a bulk feed must already exist for this account, created via POST /network-bulk-feeds. The networkReference below must match that feed.
Repeat-call semantics match the VCF endpoint above: cardCount replays the head of a reserved Mastercard test-card pool (BIN 510755, listed in Test Card Pools below; deduplicated once enrolled), newCardCount enrolls cards the subaccount has never seen, and every included card gets transactionsPerCard transactions with fresh references and recent dates on each call. Enrolled cards carry cardholder firstName, lastName, postalCode, and corporate.employeeId / corporate.alternativeEmployeeId; Mastercard feeds do not expose a cardholder email, so email stays null (as in production).
Body params
| Param | Type | Required | Description |
|---|---|---|---|
networkReference | string | Required | The Mastercard Delivery ID for the bulk feed, such as G1234567. Must match the feed created via POST /network-bulk-feeds. Pattern: ^[a-zA-Z0-9-]+$ |
cardCount | integer (0–100) | Optional (default 5) | Cards from the head of the test-card pool, the same cards every call; deduplicated once enrolled. cardCount + newCardCount must be at least 1. |
newCardCount | integer (0–20) | Optional (default 0) | Not-yet-enrolled cards drawn from the pool for this feed's subaccount. Each call enrolls this many new cards. |
transactionsPerCard | integer (0–10) | Optional (default 3) | Transactions generated per included card, with fresh references and recent dates each call. Use 0 for a cards-only starter file. |
Example request
{
"networkReference": "G1234567",
"cardCount": 0,
"newCardCount": 5,
"transactionsPerCard": 3
}Responses
| Status | Description |
|---|---|
202 | Accepted: the synthetic file was written to the ingestion bucket and the pipeline will process it asynchronously. Empty response body. |
400 | Bad request: validation error on the request body; newCardCount > 0 with no active feed for the networkReference; or the subaccount has exhausted the new-card pool. |
401 | Unauthorized: missing or invalid access token. |
403 | Forbidden: token does not have the simulation:write scope. |
500 | Server error. |
Simulate Amex GL ingestion
POST /simulate/ingestion/amex-gl
Writes a synthetic Amex bulk-enrollment file (plus a companion clearing file when transactionsPerCard > 0) to the ingestion bucket without an Amex file drop. Processing runs asynchronously: enrolled cards appear as card subscriptions once processing completes, and clearings follow shortly after enrollment.
Prerequisite: a bulk feed must already exist for this account, created via POST /network-bulk-feeds. The networkReference below must match that feed.
The repeat-call mechanics match the VCF endpoint above, though the cardCount default and the tokenized behavior differ (see the body params and callout below): cardCount replays the head of a reserved account pool (BIN 370000, listed in Test Card Pools below; deduplicated once enrolled), newCardCount enrolls accounts the subaccount has never seen, and every included card gets transactionsPerCard clearings with fresh transaction ids and recent dates on each call. Enrolled cards carry cardholder firstName, lastName, postalCode, and corporate.employeeId; Amex GL feeds do not carry a cardholder email, so email stays null.
Body params
| Param | Type | Required | Description |
|---|---|---|---|
networkReference | string | Required | The Amex Recipient ID for the bulk feed, in the format R{RecipientId}, such as R12345. Must match the feed created via POST /network-bulk-feeds. Pattern: ^R\d+$ |
cardCount | integer (0–100) | Optional | Cards from the head of the account pool, the same accounts every call; deduplicated once enrolled. When omitted, the count resolves off the feed's panFormat: an encrypted feed draws 3, a tokenized feed draws its whole enrollable tokenized pool (see the tokenized note below). The cardCount + newCardCount must be at least 1 rule applies only when cardCount is sent explicitly; omitting it entirely is fine, so a bare { "networkReference": "R12345" } succeeds. |
newCardCount | integer (0–20) | Optional (default 0) | Not-yet-enrolled accounts drawn from the pool for this feed's subaccount. Each call enrolls this many new cards. Not available on a tokenized feed: the tokenized pool has no new-card region, so any newCardCount > 0 on a panFormat: tokenized feed is rejected with 400. |
transactionsPerCard | integer (0–10) | Optional (default 3) | Clearings generated per included card (written as a companion clearing file), with fresh transaction ids and recent dates each call. Use 0 for a cards-only file. |
Tokenized Amex feedsA feed created with
panFormat: tokenized(sandbox-only, Amex-only) draws Amex network tokens instead of encrypted PANs, and two knobs behave differently on it. An omittedcardCountdraws the feed's whole enrollable tokenized pool rather than the encrypted default, andnewCardCountis unsupported: there is no tokenized new-card region, so anynewCardCount > 0returns400. An explicitcardCountlarger than the tokenized pool under-delivers to the pool size rather than erroring.
Example request
{
"networkReference": "R12345",
"cardCount": 0,
"newCardCount": 5,
"transactionsPerCard": 3
}Responses
| Status | Description |
|---|---|
202 | Accepted: the synthetic file(s) were written to the ingestion bucket and the pipelines will process them asynchronously. Empty response body. |
400 | Bad request: validation error on the request body; newCardCount > 0 with no active feed for the networkReference; newCardCount > 0 on a tokenized feed (the tokenized pool has no new-card region); or the subaccount has exhausted the new-card pool. |
401 | Unauthorized: missing or invalid access token. |
403 | Forbidden: token does not have the simulation:write scope. |
500 | Server error. |
Simulate Visa VBDS ingestion
POST /simulate/ingestion/vbds
Simulates a Visa (VBDS) delivery for an existing bulk feed. VBDS is pull-based rather than file-drop: creating a feed via POST /network-bulk-feeds already triggers an initial synthetic starter-data fetch in sandbox (sized by the creation-time cardCount field, 1–20, default 5), so this endpoint exists to re-drive the pipeline on demand: enroll additional cards and generate clearings without recreating the feed. Processing runs asynchronously: enrolled cards appear as card subscriptions once processing completes, and clearings follow shortly after enrollment.
Prerequisite: a bulk feed must already exist for this account, created via POST /network-bulk-feeds. The networkReference below must match that feed.
Repeat-call semantics match the other networks above: cardCount replays the head of a reserved card pool (deduplicated once enrolled), newCardCount enrolls cards the subaccount has never seen, and every included card gets transactionsPerCard clearings with fresh transaction ids and recent dates on each call.
Body params
| Param | Type | Required | Description |
|---|---|---|---|
networkReference | string | Required | The Visa RPIC identifier for the bulk feed, in the format {regionId}_{processorId}_{issuerId}_{companyId}, such as 1_12345_67890_54321. Must match the feed created via POST /network-bulk-feeds. Pattern: ^[a-zA-Z0-9]+_[a-zA-Z0-9]+_[a-zA-Z0-9]+_[a-zA-Z0-9]+$ |
cardCount | integer (0–100) | Optional (default 5) | Cards from the head of the card pool, the same cards every call; deduplicated once enrolled. cardCount + newCardCount must be at least 1. |
newCardCount | integer (0–20) | Optional (default 0) | Not-yet-enrolled cards drawn from the pool for this feed's subaccount. Each call enrolls this many new cards. |
transactionsPerCard | integer (0–10) | Optional (default 3) | Clearings generated per included card, dispatched through the real VBDS clearing ingestion path with fresh transaction ids and recent dates each call. Use 0 for a cards-only simulation. |
Example request
{
"networkReference": "1_12345_67890_54321",
"cardCount": 0,
"newCardCount": 5,
"transactionsPerCard": 3
}Responses
| Status | Description |
|---|---|
202 | Accepted: the synthetic delivery was enqueued and the pipeline will process it asynchronously. Empty response body. |
400 | Bad request: validation error on the request body; no active feed for the networkReference; or the subaccount has exhausted the new-card pool. |
401 | Unauthorized: missing or invalid access token. |
403 | Forbidden: token does not have the simulation:write scope. |
413 | Content too large: a numeric field exceeded its maximum (cardCount above 100, newCardCount above 20, or transactionsPerCard above 10). |
500 | Server error. |
Starter data at feed creation (cardCount)
cardCount)Distinct from this endpoint's cardCount body param above, cardCount is also an optional sandbox-only field on POST /network-bulk-feeds (integer, 1–20, default 5, type: VBDS feeds only): the number of synthetic Visa cards to generate in the starter data, drawn deterministically from the head of the Visa test-card pool below, the same PANs every feed, deduplicated once enrolled, and accepted by sandbox enrollment by construction. Requests containing this field outside the sandbox environment, or for any other feed type (including Visa VCF), are rejected with a 400.
newCards is a deprecated alias of cardCount and behaves identically; when both are provided, cardCount wins. Use cardCount in new integrations: it matches the field of the same name and meaning on the simulate-ingestion endpoints.
Test card pools
Every simulated card comes from a fixed, reserved pool per network (Visa BIN 404507, Mastercard 510755, Amex 370000). The full PAN lists, the head/new region semantics, and the per-network Luhn notes live on Test Card Pools.
Next steps
- Sandbox Testing: what the environment is and how to get a token.
- Testing Bank Linking: the guided bank-rail walkthrough.
- Testing Bulk Enrollment: the guided bulk-feed walkthrough.
Updated 3 days ago
