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
- 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.
- Ask for the grade, with the test kit's
plynth.grade(below), or as a signed call:POST /v1/sandbox/gradewith{"since": <unix time>}, a day back at most (an hour if you leave it out). - 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
| Check | What the sandbox looks for | How to show it |
|---|---|---|
fresh_ids | A new passage ID for each trip, never one used for another | make a few trips |
door_reports | Reports passed at each door it's let through | go through door |
ride_reports | Reports boarded and exited on each ride | ride lift from 1 to 2 |
stays_heard | Checks in at the car door on each ride, and never goes silent in a car | ride lift, checking in at the car door |
waits | Waits as long as a refusal's retry_after says before asking again | ask for door-closed |
backs_off | Waits out a 429's Retry-After, then sends the same request again | ask for door-busy |
resumes | Picks up the stream where it left off, with Last-Event-ID | ride lift-dropout |
drills | Answers each drill in time, its own software answering | start 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=.