Dev panel
The Dev panel is how you populate, evolve, and wipe the sandbox. Three buttons drive the entire workflow: Generate, Advance lifecycle, and Reset. Each is also a single HTTP endpoint, so agents drive the same actions from the API without opening the portal.
Generate
Creates a batch of synthetic transactions plus a configurable proportion of disputes, evidence-required records, and the matching payment-event timeline.
POST /v1/dev/generate
| Field | Type | Constraint |
|---|---|---|
| count | integer | 1 – 20,000 (API keys: 1 – 500) |
| dispute_rate | number | 0 – 1 (e.g. 0.15 = 15%) |
| currency | string | USD | EUR | GBP | CAD | AUD | JPY |
| range_days | integer | 1 – 365 (spreads created_at over the last N days) |
| seed | integer | optional; the same seed and parameters give the same shapes |
| lifecycle | string | auto | manual (default auto for people, manual for API keys) |
Card-brand distribution: ~60% Visa, ~30% Mastercard, ~7% Amex, ~2% Discover, ~1% JCB. Cardholder names and merchants are drawn from fixed synthetic sets that pass format checks but are never real parties.
Generate at volume
POST /v1/dev/generate answers 202 with the job (gj_…) at once. The rows are created in the background in chunks of 500 transactions, which survive deploys and restarts, so you can keep working in the portal (a progress card in the corner follows the job while you use other pages) or poll the job from a script.
GET /v1/dev/generate/:id
| Field | Meaning |
|---|---|
| status | pending (no chunk has run yet), running, then completed, failed or canceled. |
| progress | {done, total, unit: "transactions", pct}: transactions committed so far of the total; pct is a whole percent, rounded down. |
| started_at | When the first chunk started; null while pending. |
| eta_at | Estimated finish while running, from the recent throughput; null until two chunks have committed, and once the job has ended. |
| error_code | Why it did not complete: generate_failed, job_lease_expired, canceled or tenant_data_limit (branch on it, not on the error sentence). |
| result | {transactions, disputes} once completed. |
- Limits. A person in the dev panel may generate up to 20,000 transactions per job, and the workspace may have 20,000 in flight across its unfinished jobs. API keys may ask for 500 per job and together hold at most 5,000 of those 20,000. Over the budget, a new generate answers 409
generate_in_progresswithRetry-After. - Cancel.
POST /v1/dev/generate-jobs/:id/cancelstops a pending or running job: its remaining chunks are canceled and it endscanceled, keeping the transactions of the chunks that already committed (rows_donein the response). Clean them up right away. A job that already ended is returned unchanged. - Seeds. Each chunk draws its rows from the job's
seedand its own position, so the same seed and parameters reproduce the same shapes (merchants, amounts, statuses, reason codes) chunk by chunk, even when a chunk is retried. Ids are always new and timestamps are relative to the run; shapes may change between API versions. - Every chunk is a scheduled job:
GET /v1/scheduled-jobs?generate_job_id=gj_…lists them (see Flows).
Clean up one job
POST /v1/dev/generate-jobs/:id/cleanup
Deletes exactly what one job created (transactions, their disputes, dispute events, evidence and payment events) and nothing else. A job still running is canceled first. A job of up to 2,000 transactions is cleaned up in the request (200 with the counts). A bigger one is cleaned up in the background in chunks of 500: the call answers 202 with {id, state, scheduled_job_id}, and the generate_job.cleanup audit row with the totals is written when the last chunk commits. Send {"async": false} to always clean up in the request, or {"async": true} to always do it in the background. Calling it again while a background cleanup runs returns that cleanup (202), or 409 cleanup_in_progress with async: false.
Advance lifecycle
Runs the hourly background job on demand. Useful when you don't want to wait for the cron tick. It does three things:
- Expires
rfi_opendisputes past their RFI window →rfi_expired. - Expires
evidence_requireddisputes past theirrespond_by→expired. - Rules on
under_reviewdisputes whose 55-day issuer window has elapsed: ~60% →won, ~40% →lost.
POST /v1/dev/advance-lifecycle
Returns { rfi_expired, expired, ruled } — counts of rows in each transition so the UI can show what moved.
Reset
Wipes the synthetic data inside your tenant — transactions, disputes, evidence records, payment events, reports. Your account, your workspace, and any team members survive.
POST /v1/dev/reset
The reset is scoped to the calling tenant. Other tenants on the same instance are not affected.
It runs in the background: the call answers 202, unfinished generate jobs are canceled, and the data is deleted in batches of 1,000 rows per table, each in its own short transaction. While it runs, a new generate answers 409 reset_in_progress and another reset returns the one in progress. The audit trail is kept; the reset is recorded once, as tenant_data.reset with the totals, when it finishes. Only the owner can reset, never an API key: tests clean up their own jobs instead.