Test mode
The sandbox, the simulated clinician and pharmacy, and how to drive a whole consult without one.
A cuvo_sk_test_ key reaches a sandbox. It holds only the data you invented for it, it is scoped to
your integration, and every row is deleted 30 days after it is written. Do not send real patient
information to test mode.
Test mode is not a mock. The same routes, the same validation, the same problem bodies, the same events, the same signed webhook deliveries. What differs is that behind the seam there is no chart and no pharmacy, so this API gives you the controls to play both.
The sandbox controls
Everything under /v1/test requires the test:manage scope and is refused outright to a live
credential with 403 forbidden. There is no way to reach these from production, which is what keeps
a simulated approval out of a real chart.
| Operation | What it plays |
|---|---|
POST /v1/test/cases/{id}/decision | The clinician approving, declining, or asking a question |
POST /v1/test/cases/{id}/clinician_message | The clinician replying on the case thread |
POST /v1/test/cases/{id}/ship | The pharmacy shipping, with optional tracking |
POST /v1/test/cases/{id}/deliver | The parcel arriving |
POST /v1/test/cases/{id}/block | The order stopping, with a reason |
POST /v1/test/cases/{id}/fail | The order failing for good |
POST /v1/test/visits/{id}/start | The visit call beginning |
POST /v1/test/visits/{id}/complete | The clinician completing the visit |
POST /v1/test/visits/{id}/no_show | The patient never arriving |
POST /v1/test/webhooks/trigger | Any event type, against a fabricated resource |
PATCH /v1/test/autopilot | The timers below |
POST /v1/test/reset | Wiping the sandbox |
Deciding a case
An approval carries the plan the clinician settled on, which does not have to match what was
requested. A line with dispensable: false is recorded for the record and will never be filled.
curl https://api.cuvo.co/v1/test/cases/case_7d1e4b2a/decision \
-H "Authorization: Bearer cuvo_sk_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: decide-case-ada-2026-09-08" \
-d '{
"decision": "approve",
"plan": [
{
"medication_id": "semaglutide-0-25mg",
"quantity": 4,
"refills": 0,
"days_supply": 28,
"directions": "Inject 0.25 mg subcutaneously once weekly."
}
]
}'
A decline takes a reason, which is written to the case and is safe to show the patient. A
needs_info decision takes a target of patient or integration and the question being asked,
and moves the case to waiting_on_patient or waiting_on_integration.
Autopilot
Autopilot is off by default. Turn it on and the sandbox approves a case on its own after
approve_after_s seconds, then ships and delivers its order after ship_after_s each. Both are
seconds, 0 to 3600, and null turns that timer off. A background tick runs every minute, so a
timer fires on the next tick after it comes due.
curl -X PATCH https://api.cuvo.co/v1/test/autopilot \
-H "Authorization: Bearer cuvo_sk_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: autopilot-demo-2026-09-08" \
-d '{"approve_after_s": 30, "ship_after_s": 60}'
Use it for a demo that runs untouched. Turn it off when you are testing a branch that depends on a case sitting still. Nothing advances a live case on a timer.
Proving a receiver
POST /v1/test/webhooks/trigger emits a real event of a type you choose and schedules real,
signed deliveries for it, so you can prove your receiver before a case exists. Name an endpoint
to send to one endpoint only.
Starting over
POST /v1/test/reset removes this integration's sandbox rows and tells you how many went.
{ "reset": true, "resources_deleted": 47 }
It does not touch your integration, your credentials or your webhook endpoints.
What test mode cannot tell you
DELETE /v1/files exists in test mode and has no live counterpart, so a live delete answers 501 live_not_available. It is the one operation left that does: a document filed against a chart is
part of a clinical record, and removing one is a decision the clinic makes in its own console. The
honest answer rather than a permission error, and the reference lists every
operation with the modes it serves.
A sandbox export is worth calling out the other way. It assembles a real bundle out of the
fixtures you made, at the same /v1/exports/{id}/download link live mode hands out, so the code
that collects it is the code you ship. The one difference is that a sandbox document carries no
link to its bytes: the sandbox accepts uploads and serves none back.