Get started
Sandbox
A company created with "sandbox": true behaves exactly like a live one, with a simulated bank instead of a real one. Use it to build and test the whole loop.
Create a sandbox company
There is no separate sandbox host and no separate key. Sandbox is a property of the company. The same API key holds live and sandbox companies side by side, and everything that applies to a live company applies to a sandbox one: the chart of accounts, tax codes, numbering, the lock, events.
POST /v1/companies
{ "name": "Asiakas Oy", "businessIdentifier": "1234567-8", "jurisdiction": "FI", "sandbox": true }
{ "id": "cmp_…", "sandbox": true, …
"connections": [ …, { "id": "con_…", "kind": "BANK", "provider": "sandbox", "status": "ACTIVE", "consentUrl": null } ],
"externalAccounts": [ { "id": "xac_…", "kind": "BANK", "externalId": "FI0000000000000000", "accountNumber": "1910", … } ], … }
A sandbox company cannot be turned into a live one. Create a live company for the customer when you go to production; nothing carries over.
The simulated bank
The company's bank account is FI0000000000000000, mapped to ledger account 1910. It never produces transactions on its own. Money in is whatever you say arrived:
POST /v1/companies/{companyId}/origins
{ "type": "BANK_TRANSACTION", "externalId": "test-tx-1", "iban": "FI0000000000000000",
"amount": 125500, "bookingDate": "2026-09-15", "reference": "RF74000001",
"postings": [ { "accountNumber": "1910", "amount": 125500 }, { "accountNumber": "1700", "amount": -125500, "partyId": "pty_…" } ],
"settles": [ { "originId": "org_…" } ] }
This is the same request a bank feed you operate yourself would send on a live company. See Banking.
Outgoing payments
A payment on a sandbox company is created and held exactly as on a live one, but the bank never answers by itself. You play the bank:
POST /v1/companies/{companyId}/sandbox/payments/{paymentId}/execute
{ "bookingDate": "2026-09-30" } # body optional
POST /v1/companies/{companyId}/sandbox/payments/{paymentId}/reject
{ "reason": "insufficient funds" }
Execution moves the payment to EXECUTED, books the bank transaction that paid the bill, allocates it against the bill, and fires payment.executed followed by the bill's origin.settled. The transaction books today, or on executeOn when that date has already passed; pass bookingDate to choose. Rejection moves the payment to REJECTED with your reason and fires payment.rejected.
Both need payments:write. On a live company they answer 403 COMPANY_NOT_SANDBOX.
What differs from live
| Live | Sandbox |
|---|---|
The bank connection is started with POST /connections and completed by the customer at a consent link. | An active bank connection with provider sandbox exists from creation. |
| Bank transactions arrive through the connection. | You send them as origins. |
Payments are sent to the bank on executeOn. | Payments wait until you execute or reject them. |
Sandbox endpoints answer 403 COMPANY_NOT_SANDBOX. | Sandbox endpoints work. |
Nothing else differs. Invoice numbers, RF references, the lock, corrections, year-end close and every event behave the same.
Sandbox companies in automated tests
Companies are cheap and nothing is deleted in this version, so create a fresh sandbox company per test run rather than resetting one. Use the run id in your idempotency keys and external references so a retried run never collides with an earlier one. Testing covers dry runs and the rest of the toolkit.