Plynth Sandbox

Getting certified

Sandbox-verified means the sandbox watched your own software handle what a building depends on: a new ID for each trip, its reports at doors and in cars, waiting when it's told to wait, picking up where it left off, answering drills in time. The conformance check tests the gateway with the kit's code; this grades yours.

How it works

  1. Run your own software as one of your robots on the sandbox, not the kit, and send it through the places in the table below. Most checks need one trip each.
  2. Ask for the grade, with the test kit's plynth.grade (below), or as a signed call: POST /v1/sandbox/grade with {"since": <unix time>}, a day back at most (an hour if you leave it out).
  3. Read it. Each check is passed, failed (with the trips behind it), or not seen (with how to show it). Fix what failed, show what wasn't seen, and grade again. When every check has passed, the answer carries a certificate.
python3 -m plynth.grade --env ../plynth-sandbox.env --minutes 60

The sandbox grades what it saw, from its own record of your robot's calls: nothing your software says about itself counts. Trips and drill answers made under X-Test-Run, as the conformance check's are, are left out, so running the kit can't earn the certificate.

The checks

CheckWhat the sandbox looks forHow to show it
fresh_idsA new passage ID for each trip, never one used for anothermake a few trips
door_reportsReports passed at each door it's let throughgo through door
ride_reportsReports boarded and exited on each rideride lift from 1 to 2
stays_heardChecks in at the car door on each ride, and never goes silent in a carride lift, checking in at the car door
waitsWaits as long as a refusal's retry_after says before asking againask for door-closed
backs_offWaits out a 429's Retry-After, then sends the same request againask for door-busy
resumesPicks up the stream where it left off, with Last-Event-IDride lift-dropout
drillsAnswers each drill in time, its own software answeringstart a drill: POST /v1/drills

The scenario places are on the sandbox page. A check-in is POST /v1/passages/<id>/heartbeat (Check-ins; heartbeat(passage_id) in the kit's client). For waits, a refusal with retry_after followed by no new request for that place too soon is a pass: you needn't wait out a long retry_after. For drills, start one with POST /v1/drills and let your software answer it; the conformance check's --robot-answers does the same with the kit starting the drills.

Not graded yet: an unknown state or reason code, handled as the guide says; a wrong gateway certificate, refused. Test both yourself before a site visit: Refusals in the guide says how a robot treats a code or a state it doesn't know, and Verifying the gateway how it checks the gateway's certificate.

The certificate

{"id": "5c1e9a0b7d2f4e36", "level": "sandbox-verified", "robot": "kestrel-1", "company": "kestrel",
 "software": "fleet-4.2.1", "checks": ["fresh_ids", "door_reports", "..."], "since": 1767225600,
 "issued": 1767229200, "issuer": "the Plynth sandbox", "release": "sandbox-2026-10-21.2", "signature": "..."}

software is the version your robot last sent in its heartbeat (POST /v1/heartbeat with software; in the kit's client, online(software=...)): send it, so the certificate names what was tested. Anyone can look a certificate up at https://sandbox.plynth.build/docs/certificates/<id>, which answers with it and "valid": true while its signature checks. Give a building the link rather than a copy. Grading again on the same software gives the same certificate; each robot keeps its newest 50.

Sandbox-verified is the first level. A building still runs its own go-live with your robots, under its own rules and record (Going live), and when your robot reports a new software version the building is asked to test it again. The certificate shows your software is ready for that, not that it has passed it.

Marking a test

Send X-Test-Run: <label> on a call to mark it as a test, as the conformance check does (conformance). In a building, its record marks each passage made under one as a test. It's optional and, like every header but the three signed ones, it isn't signed. In the reference client, pass test_run=.