Skip to content

Sandbox

A sandbox is a real Talki account that belongs to you, served by the same /v1 contract as production and marked by livemode: false. Registering an app in the console issues one immediately, with no Talki customer involved.

A hair salon in Verona, seeded with:

  • four services with different durations and capacities, one of which takes only one booking at a time;
  • opening hours with a real closing day — Monday and Sunday are shut;
  • four weeks of answered calls with summaries, in all three inbox states (unread, read, archived);
  • the appointments those calls booked, past and upcoming, plus one cancelled in each direction;
  • a contact book where some callers are named and some are bare numbers;
  • open follow-up questions the assistant could not answer.

display_name carries only a name the owner curated or imported; one the assistant extracted from a transcript is not published, and that contact stays a bare number. The seed contains both cases.

The sandbox has multi_sede: true and three locations: the main one, which holds the history above, and two branches, Borgo Trento (active) and San Zeno (inactive). Each branch has its own copy of the main location’s services, on a default resource of its own, and a quieter history of calls, bookings, unresolved requests and one closure. San Zeno behaves as a location switched off by a downgrade: its records are there, and booking at it is refused with location_inactive.

No location has a Talki number. Scenario calls are always made at the main location. If GET /v1/locations lists only the main location, POST /v1/sandbox/reset adds the two branches.

A sandbox has no phone number and cannot be given one. It cannot receive real calls, is never billed, and sends no email.

Its knowledge base holds at most 5 MB, with files of at most 1 MB, website imports of at most 5 pages and PDFs of at most 20 pages, or the plan’s own limits where lower. A partner’s test account has the same caps.

Terminal window
curl --fail-with-body -X POST https://api.talki.it/v1/sandbox/calls \
-H "Authorization: Bearer $TALKI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scenario": "booking_created", "caller_phone": "+393480000001"}'

Everything downstream is real: the call record, the appointment (subject to the same capacity rules as any booking), a pending contact for a caller who gives a name, and signed webhook deliveries to your endpoint.

Reuse a caller_phone to build a caller with history; vary it to exercise the first-time path.

scenario What happened Events
booking_created A caller books. The agent finds a slot and confirms it. call.ringing, call.ended, appointment.created
booking_rescheduled A caller moves an existing booking. call.ringing, call.ended, appointment.updated
booking_cancelled A caller cancels. The slot is released. call.ringing, call.ended, appointment.cancelled
caller_unresolved A question the agent cannot answer, taken down for a callback. call.ringing, call.ended
contact_created A first-time caller gives their name. call.ringing, call.ended
call_no_action Someone asks a question and rings off. call.ringing, call.ended
call_transferred The agent hands the caller to a person. call.ringing, call.ended, call.transferred

GET /v1/sandbox/scenarios returns the same list at runtime, so a test suite can enumerate them instead of hard-coding this table. The events array is part of the contract: assert that those events arrive. It lists the events the scenario is about, not every event the call sends. A caller who gives a name (booking_created, booking_rescheduled, booking_cancelled, contact_created) is also proposed as a contact, as on a real call: pending_contact.created for a number with no contact or pending contact yet, pending_contact.updated when an existing pending contact’s name changes.

call.ringing carries the same ID in data.call_id as the response’s call_id. It is sent after the scenario’s booking step, so an appointment.* event can come before it, and a scenario refused with 400 sends nothing. caller.vip is true when caller_phone is on the main location’s VIP list: add it with POST /v1/caller_rules to see it. A caller_phone that is not a number behaves like a withheld one, and caller.phone_number is null.

booking_rescheduled and booking_cancelled act on the next upcoming appointment. If the agenda has none left, they answer 400 sandbox_not_ready; reset to continue.

Terminal window
curl --fail-with-body -X POST https://api.talki.it/v1/sandbox/reset \
-H "Authorization: Bearer $TALKI_API_KEY"

The history comes from a fixed seed, so it is identical every time and assertions against it hold across runs. A reset rebuilds data only: your API key keeps working, your app’s installation survives, the services and the locations are still there, and the branches’ history is rebuilt with the rest. The knowledge base is left as it is: delete the documents your tests create. Webhook endpoints are not kept: a reset deletes the sandbox’s endpoints and their deliveries, puts the seed’s one paused endpoint in their place, and drops the account events still queued for your app. Register your endpoints again after each reset.

Every webhook envelope carries livemode:

{
"id": "evt_9f2c…",
"type": "appointment.created",
"account_id": "f7e39b9a-1ac8-45e4-93da-2600f17cb286",
"livemode": false,
"origin": { "source": "voice", "credential_id": null },
"data": { }
}

false means the event did not really happen: it came from a sandbox or a test-mode credential. GET /v1/account reports the same flag for the credential you are holding, so staging and production can share one receiver and still keep fixtures out of the live system.

Same code, same base URL; only the credential changes. Real businesses can install your app once it is approved.