1 Cancel and errors
Two things a driving integration has to handle: stopping a run that should not continue, and reacting when the machine reports a problem. They interact, so they are together.
1.1 Cancel is two steps
Canceling takes a lease first, then commits it:
curl --insecure -b cookies.txt https://kilobaser.lab.example.org/api/control/cancel
{
"currentState": { "mode": "emergencyStop" },
"cancelLeaseExpiresAt": "2026-08-10T00:21:40.882111085+02:00"
}
Note what that did. Taking the lease stops the machine immediately — the mode goes to
emergencyStop — and starts a countdown, visible to every connected client as
cancelLeaseExpiresAt. It is the interface's "are you sure" made explicit in the
protocol, so that a second client can see a cancel is being considered.
Then either commit it:
curl --insecure -b cookies.txt -X PUT https://kilobaser.lab.example.org/api/control/cancel/confirm
or release it and let the run continue:
curl --insecure -b cookies.txt -X PUT https://kilobaser.lab.example.org/api/control/cancel/abort
Do not take the lease speculatively. Between taking it and deciding, the Kilobaser is stopped.
After a confirmed cancel the Kilobaser runs a recovery protocol to get itself back to a
safe state — lid closed, cartridge in a known position. It can take up to ten minutes,
it has checkpoints of its own, and they have to be answered like any other run. A canceled
synthesis is not finished when confirm returns; it is finished when the machine is idle
again. See General cancel in Operation.
Canceling is not always expensive. If it happens within 30 seconds of the lid closing, the chip and the vial can both still be reused; after that the linker molecules have been consumed and the chip cannot. Either way the cartridge stays installed — it is only discarded when it is swapped. See Consumables in the Kilobaser manual.
1.2 Errors block everything else
If any error is active, cancel refuses:
{"error": "{\"code\": \"1-1-17\", \"message\": \"Could not proceed, when errors are active!\"}"}
This surprises people, because an error is often exactly when you want to cancel. The order is: clear the error, then cancel.
Active errors are in GET /api/init under errors, and arrive as an errors event
whenever the set changes:
[
{
"id": "2026-08-09T22:10:49Z-56cd646c",
"code": "1-2-24",
"message": "Unexpected power outtage",
"active": true,
"type": "powerOutage"
}
]
When the last error clears, the event fires with an empty payload — which may be null
rather than []. Coerce it, or a client will crash on recovery rather than on failure.
The same error at the Kilobaser, where somebody has to press CONFIRM:

1.3 Acknowledging an error
PUT /api/errors/handle/:id, with the error object and a decision about the run:
curl --insecure -b cookies.txt \
-X PUT https://kilobaser.lab.example.org/api/errors/handle/2026-08-09T22:10:49Z-56cd646c \
-H 'Content-Type: application/json' \
-d '{"cancelRun": true, "error": {"id": "2026-08-09T22:10:49Z-56cd646c", "code": "1-2-24"}}'
cancelRun decides what happens to whatever was running: true abandons it, false
acknowledges the error and leaves the run in place to be continued or canceled
separately.
The response echoes the error back with active still true. That is the object you
submitted, not the new state. To confirm it actually cleared, look at the errors event
or re-read /api/init — do not trust the echo.
1.4 Errors are not exceptions
An error here means the Kilobaser has a physical problem — a lid that did not reach its endstop, pressure out of range, a cartridge removed while in use. It is not the mechanism for reporting a bad request; those come back as HTTP errors on the call that caused them.
So an active error usually needs a person, and a client that clears errors automatically
so it can keep going is defeating the purpose. kb_run_head.py takes the opposite line:
! error 1-2-24: Unexpected power outtage
refusing to answer while an error is active -- clear it first
Report it to whoever is responsible and stop. The exception is an error you understand specifically and can act on — then handle that code, and only that code.
1.5 When the device restarts
Worth knowing because it is the one you will hit while developing. If the Kilobaser reboots or is updated mid-run:
- every session ends, because sessions are held in memory. Your next call fails with
2-1-4, at HTTP 400. Log in again. - your event stream stops. Reconnect and re-prime from
/api/init; nothing is replayed. - an error is waiting,
1-2-24Unexpected power outtage, and it will block cancel until acknowledged. - the interrupted run is still there, in whatever state it reached.
A client that handles this case handles most of the others for free, which is why it is worth deliberately restarting a test machine once and watching what your code does.
1.6 Errors worth retrying
| Situation | Retry |
|---|---|
2-1-4 session cookie missing |
yes, after logging in again |
| network failure or dropped stream | yes, with backoff |
1-1-8 something already running |
yes, later |
1-1-17 errors are active |
only after clearing them |
1-1-9 not first in queue |
no, reorder instead |
2-11-11, 2-11-12, 2-11-28 bad sequence |
no, the request is wrong |
2-11-20 invalid combination |
no, the cartridge and chip do not go together |
2-1-5 insufficient permissions |
no, wrong account |
See error codes for the full list.