Test in the sandbox
Every account has a sandbox. Sandbox keys (rl_sk_test_...) see only sandbox objects, nothing in the sandbox moves money, and a payment provider connects at once. In the sandbox stripe is Runledger’s own Stripe sandbox, shared by every account, or a fake when the deployment has no Stripe key, so test payments don’t appear in your own Stripe dashboard. The sandbox is where a coding agent is allowed to write: with the right scopes it applies configuration directly, and each write is recorded as made by that agent.
Test clocks
Settlement runs on clocks: confirmation windows, reversal horizons, dispute deadlines, hold and grant expiry, billing periods and invoice finalization. A test clock lets you move time forward and watch all of it happen.
- Create a clock with
POST /v1/test_clocksand afrozen_time. - Create customers with
test_clockset to its ID. Everything time-based for those customers follows the clock. - Advance it with
POST /v1/test_clocks/{id}/advance. Clocks only move forward, up to 90 days per call. Everything that falls due runs in order, the clock isadvancinguntil it finishes, and thentest_clock.readyfires. - Delete it when you’re done, which deletes its customers too. Clocks are deleted automatically after 30 days.
Windows, spend limit periods, credit expiry and auto top-up retries all follow the clock too, so a week of usage windows runs in one call.
Use a separate clock for each scenario, so test runs don’t interfere with each other. Each account has one sandbox, so a separate sandbox for continuous integration is a second account.
Start clean and check your work
POST /v1/sandbox/resetdeletes the sandbox’s customers, events, test clocks and request logs so a test run can start from nothing. It works only with a sandbox key. The dashboard asks a person to type a confirmation first; the API takesconfirm: true.POST /v1/integration_checksreviews the sandbox’s recent traffic against the going-live checklist and returns a pass, warn, fail or unknown result for each item, with the evidence and the fix. It is the same result as the dashboard’s Get started page and the MCPcheck_integrationtool.
Simulate the payment provider
All of them send the same webhooks as the real thing. They work only with sandbox keys; a live key gets 403.
Two scenarios worth running
Two agents, one balance
Give a prepaid customer 100 credits, then fire two check() calls at once, each holding 60. One is allowed and one is denied with insufficient_balance. Track the allowed one and the balance ends at 40. Checks and holds explains why.
The unhappy customer
- Set up the
chat_resolutiondefinition and postpaid plan from the quickstart, on a clock. The definition’sdispute_settings.decision_windowofP7Dsets the deadline below. - Track
resolution.chatwithreference: "conv_9001". The charge ispending. - Track
csat.submittedwithreference: "conv_9001"andproperties: {"score": 1}. Thelow_csatrule opens a dispute; the charge isdisputedand won’t confirm. - Advance the clock six days.
dispute.decision_duefires. - Either resolve the dispute with
uphold, and the charge confirms, or let the deadline pass, and the defaultreverseapplies: the dispute showsdecided_by: "default"and the charge isreversed. Resolving a dispute needsdisputes:resolve, which the key from Get started doesn’t carry, so use a secret key from API keys. - Advance to the end of the month. The finalized invoice has no line for the reversed charge.
What the sandbox doesn’t simulate
- Payment provider behavior beyond success and failure, such as card authentication steps and bank-specific declines.
- Bank debits and PayTo, which Runledger doesn’t support.
- Load. Sandbox rate limits are lower, so don’t load test against it.
Each account has one sandbox, which comes with the account; a second sandbox is a second account.
See also
- Quickstart runs a charge through a test clock.
- Going live uses test clocks for a backtest.