1 Error codes
Every failure from the device carries a code like 2-11-12. The code is the stable part
of the answer and the thing your program should branch on. This chapter lists the codes a
user or admin client can actually meet, what each one really means, and what to do
about it.
1.1 How to read an error
An error response is an object whose error field holds another JSON document, as a
string:
{"error": "{\n \"code\": \"2-11-12\",\n \"message\": \"The sequence needs to be at least 8 bases long!\"\n}"}
So getting at the code means parsing twice:
detail = json.loads(response.json()["error"])
print(detail["code"], detail["message"])
Branch on code, never on message. The messages are the device's own text, typos
and all, and they are reworded between releases. They are reproduced verbatim in the
tables below so you can recognize what you are looking at, not so you can match on them.
The code itself is <area>-<package>-<number>. The prefix tells you roughly where the
failure came from, which is often enough to route it: 2-1 is authentication, 2-11 is
process runs, 2-12 is answers and sequences, 2-14 is settings, 2-16 is accounts,
1-1 is the machine's own orchestration, 1-2 is sensors and hardware, and 4-1 is the
network and USB layer.
Three responses break the double-encoded shape, and a parser that assumes it will throw on all three:
| When | Body | Why it differs |
|---|---|---|
A body without Content-Type: application/json |
{"error": "Bad Content-Type or charset, expected 'application/json'"} |
Rejected before it reaches any endpoint, so there is no code to report |
PUT /errors/handle/:id with an unknown id |
{"error": "Resource not found"} |
Uses the framework's own not-found reply |
| Any call while the Kilobaser is booting | {"error": "{...}", "state": "starting"} |
Carries the usual envelope plus a state field, with Retry-After set |
Parse defensively: try the inner json.loads, and fall back to treating error as a
plain string.
1.2 Session and account errors
| Code | HTTP | Message | What it means | What to do |
|---|---|---|---|---|
2-1-4 |
400 | Session cookie was not provided | No session cookie reached the device at all |
Log in and retry. Note the 400: there is no 401 here |
2-1-2 |
400 | SessionID not found or expired | The cookie was sent but the device does not know it. Usually the Kilobaser restarted, since sessions live in memory | Log in again and retry the call once. If it fails again, stop |
2-1-3 |
400 | SessionID not found or expired | The same condition, reported from a different check | As above |
2-1-1 |
400 | Session cookie was not provided for SSE | You opened /subscribe without a cookie |
The event stream needs the same session as everything else |
2-1-5 |
403 | Insufficient permissions | Your role does not have this route. Either an admin-only endpoint reached with a user account, or a developer-only endpoint |
Use an admin account, or stop calling it. Never retry |
2-1-6 |
400 | Provided request was wrong | The login body was not the expected JSON | Send {"username": ..., "password": ...} with the JSON content type |
2-1-7 |
403 | Insufficient permissions | A dev account tried to log in to a Kilobaser that is not in developer mode |
Use a user or admin account |
2-1-8 |
400 | SessionID not found or expired | Misleading text. The username you sent contains characters that are not allowed | Fix the username. Retrying will not help |
2-16-2 |
403 | user/password combination not found | The account exists and the password is wrong | Do not retry. Repeated attempts are not rate limited, but they are also not going to work |
2-16-3 |
403 | user/password combination not found | There is no account with that id. Remember the id is not the display name; "Administrator" logs in as admin |
Check the id in GET /users from an admin session |
2-16-10 |
400 | Password change only possible for yourself. | POST /changeAccount/:id with somebody else's id. Admins included |
Use the id from session.user.id |
2-16-13 |
400 | No new PWD entered | PWDNew was empty |
Send a password |
2-16-14 |
400 | New Password missmatch | PWDNew and PWDConfirm differ |
Send the same value in both fields |
2-16-12 |
400 | Your request has errors. | The password change body could not be decoded | Send {"PWDNew": ..., "PWDConfirm": ...} |
2-16-15 |
500 | Could not store your password | The new password was not written | The old password still works. Retry once |
2-16-7 |
400 | Your request has errors | POST /users did not receive an array |
Post the entire user array, not a single object |
2-16-18 |
500 | Username must not contain any special characters | An account id in the array you posted is not plain alphanumeric | Fix the id. The 500 is misleading; the request is at fault |
2-16-9, 2-16-11, 2-16-16 |
404 | Could not find user | No account with that id | Re-read GET /users |
2-16-8 |
500 | Could not store user | Writing the user store failed part way through | Re-read GET /users before retrying, because earlier entries in your array may already be applied |
2-16-4, 2-16-6 |
500 | Could not retreive any user | The device could not read its own user store | Retry once. If it persists, contact support |
2-16-5 |
400 | Your request has errors | The POST /confirmPWD/:id body could not be decoded |
Send {"username": ..., "password": ...} |
1.3 Queue and run errors
Queueing an oligo
| Code | HTTP | Message | What it means | What to do |
|---|---|---|---|---|
2-11-19 |
400 | Provided run is invalid. | The queue POST body could not be decoded into a run | Check the JSON, and check the content type header |
2-11-20 |
400 | provided processType is invalid | The most common queueing failure, and badly named: the combination of processType, cartridgeType and chipKind has no process on this device |
Check the pair against supportedOPModes in the status, or ccSettings.operations in /init. Do not retry |
2-11-5 |
400 | Provided processType is invalid | The same problem, caught one step later while planning | As above |
2-11-12 |
400 | The sequence needs to be at least 8 bases long! | A synthesis of fewer than eight bases | Reject the order upstream. Retrying cannot help |
2-11-11 |
400 | The sequence is too long for the current cartridge type | The sequence needs more bases of some letter than a fresh cartridge of this type carries | Split the oligo, or use a cartridge type with a larger budget |
2-12-4, 2-12-5 |
400 | wrong characters for given sequence | The sequence contains a letter this cartridge has no reagent for | Validate against the cartridge's baseCount keys before submitting |
2-11-28 |
400 | Provided sequence has unsupported letters | The same, found in a record of a FASTA import. The whole import stops at that record | Fix the file. Records before the bad one have already been queued |
2-13-3 |
400 | Can't use the specified protocol with the specified sequence: Given letter is not defined. | The chip you chose has no chemistry for one of the letters | Check that the chip kind matches the modification you want |
2-13-1 |
400 | Instruction not supported on current chip | The planned protocol cannot run on the chip you asked for | Pick a chip from supportedOPModes |
2-13-2 |
400 | Sequence was not specified. | answers.sequence was missing or empty on a synthesis |
Add the sequence |
2-11-18 |
400 | Kilobaser is in an emergency state. No further runs can be queued | The machine put itself into a sensor emergency | Somebody has to recover the Kilobaser first |
2-11-45 |
400 | This cartridge type is already initialized. | An initialisation run was queued for the cartridge type already set up | Nothing to do; the machine is ready |
2-11-2, 2-11-3 |
500 | Could not find necessary processes to queue your operation. | The machine could not plan a path from where it is now to what you asked for. Not a storage failure despite the 500 | Re-read the status and queue again once the machine has settled. If it repeats from an idle machine, the combination is not supported |
2-11-1, 2-11-21, 2-11-24 |
500 | Could not store your queued run. | Writing the queue failed | Re-read the queue before retrying: the entry may exist anyway |
2-11-27 |
400 | The provided request has errors. | The FASTA import body was not {"content": ..., "processRun": {...}} |
Fix the body shape |
Reading the queue and the history
| Code | HTTP | Message | What it means | What to do |
|---|---|---|---|---|
2-11-13, 2-11-14 |
400 | Wrong input for "from" or "limit" clause on retreiving queued runs. | A paging parameter was not an integer | Fix the query string |
2-11-29, 2-11-30 |
400 | Wrong input for "from" or "limit" clause on retreiving runs. | The same on GET /processRuns |
Fix the query string |
2-11-15, 2-11-31 |
400 | Provided range is invalid | from was negative or limit was below 1. Values are rejected, not clamped |
Send sane paging values |
2-11-16, 2-11-32 |
404 | Could not find runs in given range. | You paged past the end of the collection | Treat it as the end of the list, not as a failure |
2-11-17, 2-11-22, 2-11-25 |
404 | Could not find process run, or Could not find your run. | The queue entry is gone: started, deleted, or replanned out of existence | Re-read the queue. Never assume an id you queued still exists |
2-11-33, 2-11-35 |
404 | Could not find run, or Could not find process run. | No started run with that id. Queued entries live in a different collection | Use the queue endpoints for queued entries |
2-11-23 |
400 | Your provided run has errors. | The queue PUT body could not be decoded | Send the entry you read back, modified |
2-11-26 |
500 | Could not remove your run from the queue. | The delete failed | Re-read the queue; it may be gone anyway |
2-11-36 |
400 | The provided run has errors. | The body of PUT /processRuns/:id could not be decoded |
Only title and metadata are applied by that call anyway |
2-11-37 |
500 | The provided run could not be stored. | The update to a started run failed to save | Re-read the run before retrying |
Starting and answering
| Code | HTTP | Message | What it means | What to do |
|---|---|---|---|---|
2-11-44 |
400 | Request has errors. | The start body could not be decoded | Send {"id": "<queue entry id>"} |
2-11-34 |
404 | Only a queued ProcessRun may be started. | The id you posted is not in the queue. Usually you sent the id of a run that already started | Post a queue entry id |
1-1-9 |
400 | ProcessRun is not first in queue. | Only the front of the queue can start | Move the entry to the front with priority, then start again |
1-1-8 |
400 | ProcessRun is already running. | Something is running already | Wait. This one is worth retrying later |
1-1-3 |
400 | Run already pending. Aborting. | A start is already in flight | Re-read the status instead of starting again |
1-1-6 |
400 | Kilobaser is in an emergency state. Only emergency process can be started | The machine is in emergency | Recover it first |
1-1-4 |
400 | Machine state mismatch. Your run could not be queued | The machine moved between the plan and the write | Re-read the status and try again |
1-1-12 |
500 | Machine state mismatch. Your run could not be started | The same at start time | As above |
1-1-10 |
500 | The started run seems to be faulty. We deleted it. Please reenter your run and sorry for the inconvenience. | The plan could not be executed and the entry has been removed | Queue it again. If it recurs, the combination is the problem |
1-1-11 |
500 | Run protocol does not exist. There seems to be a version mismatch. Please contact support. | The software and the Kilobaser data disagree | Contact support. Do not retry |
2-11-38 |
404 | The provided run could not be found. | You answered a run id that does not exist | Take the id from currentProcessRunId |
2-11-39 |
400 | The provided run has errors. | The control body could not be decoded | Send {"answers": {"<answerId>": "true"}} |
2-11-41 |
400 | No option was set | answers was empty |
Send at least one answer |
2-11-40 |
400 | Due to active errors, your request was dismissed | You tried to answer while an error is active | Clear the error, then answer |
2-11-42 |
400 | Pressure is too low! Please, check your connection. | The gas supply is below what the next step needs | Somebody has to check the gas line, then answer again |
2-11-43 |
400 | Please insert your cartridge. | You confirmed a cartridge checkpoint with no cartridge in place | Wait for currentHWDState.cartridgePresent, then answer |
2-12-2 |
400 | answer type 'confirm' must always be true | You sent "false" for a confirmation question |
Confirmations accept only the string "true". There is no way to decline |
2-12-3 |
400 | Could not parse your sequence. | A yes or no answer was not a parseable boolean string | Send "true" or "false" as strings, not as JSON booleans |
2-12-6 |
400 | answer not implemented | The answer id is not one the machine handles | Only use ids that appeared in pendingAnswers |
1-1-13 |
400 | No open questions | Nothing is waiting for an answer. Usually a race: you answered twice | Re-read pendingAnswers before each answer |
1-1-14 |
404 | ProcessRun not found | The run the machine was asked to answer no longer exists | Re-prime from /init |
2-11-7, 2-11-9 |
500 | Could not store your answers, or Could not store current run! | The answer was accepted but not written | Re-read the run before retrying |
1.4 Machine errors
Cancel and emergency
| Code | HTTP | Message | What it means | What to do |
|---|---|---|---|---|
1-1-17 |
400 | Could not proceed, when errors are active! | Any active error blocks all three cancel calls. This surprises everyone, because an error is usually when you want to stop | Acknowledge the errors with PUT /errors/handle/:id, then cancel |
1-1-28 |
400 | There is no active run to cancel! | Nothing is running | Check currentProcessRunId first |
1-1-29 |
400 | No active cancel lease. | You called confirm or abort without taking a lease, or the five minute lease expired |
Take the lease again with GET /control/cancel |
1-1-24, 1-1-25, 1-1-26, 1-1-27 |
500 | Could not load, create, insert or store runs during cancel lease! Lease is aborted | The device could not build the rollback plan. The lease was released for you | Re-read the status. The run is still going |
1-1-30 |
500 | Cancel process could not be loaded for cancel. | The recovery process for this cartridge is missing | Contact support. The run is in an undefined state |
1-1-15 |
400 | The provided error resolve code does not exist | The error you sent back to /errors/handle/:id is not one the machine can resolve |
Send the error object exactly as you read it |
1-1-16 |
400 | The provided error type has no corresponding sensor. | The same for a sensor error | As above |
2-8-1 |
400 | Request has failures | The error handle body could not be decoded | Send {"cancelRun": <bool>, "error": {...}} |
2-8-2, 2-8-3 |
500 | Could not get the state of the machine, or Could not send out error, please refresh | The device failed while resolving the error | Re-read /init and check whether the error actually cleared |
1-2-15 |
409 | Machine is in sensor emergency state | POST /startEmergencyRun when the machine is not in a sensor emergency |
Only call it when the machine has asked for it |
1-1-36 |
400 | Too many requests in a short time, please wait and retry | You hit a control path too quickly | Back off and retry |
1-1-37 |
503 | The machine is starting up. Please wait. | The HTTP server binds before the Kilobaser data is loaded, so the API answers before it can work | Honor Retry-After and poll. The state field says starting or migrating |
2-7-2, 2-7-3 |
500 | Kilobaser customer or hardware configuration not supported. Please install the named config version. | The Kilobaser data and the software do not match. It also appears in the status as versionError |
Contact support. Queueing is unreliable until it is fixed |
Sensor and hardware conditions
These describe physical problems. You will usually meet them as objects in errors from
/init and the errors event, rather than as HTTP responses — although
PUT /errors/handle/:id returns one when it cannot resolve what you sent. None of them
are your request's fault, and none are worth retrying: they need a person at the
Kilobaser.
| Code | HTTP | Message | What it means | What to do |
|---|---|---|---|---|
1-2-1, 1-2-2 |
409 | Lid is not down, or Lid is not up | The lid did not reach the position the step needs | Ask the operator to check the lid and clear the error |
1-2-3 |
409 | Main pressure is critical | Gas supply far below the working range | Stop. Check the supply before anything else |
1-2-4, 1-2-16 |
409 | Main pressure is low | Gas supply below the working range | Check the line and the bottle |
1-2-5, 1-2-21 |
409 | Temperature is too high | The vial heater is above range | Let it settle; report if it repeats |
1-2-6, 1-2-22 |
409 | Temperature is too low | The vial heater is below range | As above |
1-2-7 |
409 | Temperature is critical! | Out of range far enough to stop the run | Stop and report |
1-2-8 |
409 | Squeeze protection active! | Something is obstructing a moving part | A person must clear it |
1-2-9, 1-2-20 |
409 | Cartridge is not down | The cartridge did not reach its lower position | Check the cartridge seating |
1-2-10, 1-2-19 |
409 | Cartridge is not up | The cartridge did not reach its upper position | As above |
1-2-11, 1-2-17 |
409 | Cartridge is not present | No cartridge where one is required | Insert it and clear the error |
1-2-18 |
409 | Cartridge is present | A cartridge is in place where the step needs none | Remove it |
1-2-12, 1-2-13 |
409 | Vial is not present, or Vial is present | The collection vial does not match what the step expects | Place or remove the vial |
1-2-14 |
409 | Wrong chip is present | The chip sensor sees something other than the run's chipKind |
Insert the right chip, or cancel the run |
1-2-23 |
500 | sensor failed | A sensor stopped answering. Repeated failures put the machine into emergency | Report it. The run cannot be trusted |
1-2-24 |
500 | Unexpected power outtage | The Kilobaser restarted while a run was in progress. You will also have lost your session and your event stream | Log in again, re-prime from /init, acknowledge this error before trying to cancel |
1-2-25 to 1-2-37 |
500 | Battery, fan, power supply, lid seal, lid motors, heater, cylinder, valves, short circuit and motor current faults, each ending "please call our support" | Hardware faults the Kilobaser cannot work around | Surface the message to the operator and stop. Do not automate around these |
1.5 Settings errors
| Code | HTTP | Message | What it means | What to do |
|---|---|---|---|---|
2-14-8 |
400 | Your request has errors | The oligo yield body could not be decoded | Send {"resuspensionVolume": <n>, "oligoYield": <n>} |
2-14-9 |
500 | Could not update Oligo Yield settings | The write failed | Retry once |
2-14-19 |
400 | Your request has errors | The system information body could not be decoded | Send a system information object |
2-14-20 |
500 | Could not store your system settings | Either the write failed, or setTime was not formatted 2006-01-02 15:04:05. A malformed time gives 500, not 400 |
Check the time format before assuming a device fault |
2-14-10 |
400 | Your request has errors | The security settings body could not be decoded | Send the object returned by GET /settingsUser/security |
2-14-11 |
400 | factory reset in developer mode not possible | A factory reset was requested on a developer Kilobaser | Not applicable to production devices |
2-14-12, 2-14-13 |
500 | Could not disable or enable remote access | The remote access flag could not be written | Retry once, then check the setting at the touchscreen |
2-14-15 |
500 | Could not store security settings | The write failed after the listener was already reconfigured | Re-read the settings; the device may be in a mixed state |
2-14-16 |
500 | Could not access USB stick | Almost always means no stick is plugged in | Ask for a stick to be inserted. Not a device fault |
2-14-17 |
400 | Your request has errors | The SSL file body could not be decoded | Send the paths from GET /usbFiles |
2-14-18 |
500 | Could not import your ssl files. | The certificate or key could not be copied | Check the paths and the file contents |
2-14-6 |
400 | Your request has errors | A wifi scan or wifi config body could not be decoded | Check the body shape |
2-14-7 |
400 | Your request has errors | The network request body could not be decoded | Check the body shape |
4-1-14, 4-1-15 |
500 | Could not read USB | The USB stick could not be read | Reseat the stick, or use a different one |
4-1-16 to 4-1-19 |
500 | Could not copy your SSL files | Your certificate or key could not be installed | Check that both files exist on the stick |
4-1-1 to 4-1-12 |
500 | Host name, key encryption, certificate or key file failures | The device could not generate its own certificate | Contact support. HTTPS will not come back on its own |
4-1-13 |
500 | Could not set hostname | The hostname change failed | Retry once, then set it at the touchscreen |
4-1-21, 4-1-22 |
500 | Could not set time | The clock change failed | Check the format first, then retry |
4-1-23 |
500 | Could get NTP Status | The device could not read its time sync state | Harmless on its own; ignore unless the clock is wrong |
1.6 Which errors are worth retrying
Retrying is only ever right for a condition that is temporary and not caused by your request. That splits the codes into three groups.
| Retry | Situation | How |
|---|---|---|
| Yes | Network failure or a dropped event stream | Reconnect with exponential backoff, and re-prime from /init afterwards |
| Yes | 1-1-37 while the Kilobaser boots |
Honor Retry-After, poll every couple of seconds. This can last a minute or two after a restart |
| Yes | 2-1-4, 2-1-2, 2-1-3, 2-1-1 session gone |
Log in again and retry the call once. Retrying more turns a wrong password into an infinite loop |
| Yes | 1-1-36 too many requests |
Pause, then retry |
| Yes | 1-1-8 something is already running |
Retry later, or wait for the machine to go back to ready |
| Yes | 1-1-3, 1-1-4, 1-1-12 state moved under you |
Re-read the status first, then decide again. Do not replay the same request blind |
| Once | 500s from a write: 2-11-1, 2-11-21, 2-11-24, 2-11-7, 2-11-9, 2-16-8, 2-14-9 |
Read the collection back before retrying. These fail after the change may already have landed, and a blind retry queues the oligo twice |
| After a fix | 1-1-17 errors are active |
Acknowledge the errors, confirm from /init that they cleared, then repeat the call |
| After a fix | 2-11-42, 2-11-43, and any 1-2 condition |
A person has to do something physical. Retry when the status shows the condition gone |
| No | 1-1-9 not first in queue |
Reorder the queue instead |
| No | 2-11-20, 2-11-5, 2-13-1 unsupported combination |
The cartridge and chip pair does not exist on this device |
| No | 2-11-11, 2-11-12, 2-11-28, 2-12-4, 2-12-5, 2-13-3 bad sequence |
The order is wrong. Reject it upstream |
| No | 2-12-2, 2-12-3, 2-12-6 bad answer |
Your control call is malformed |
| No | 2-1-5, 2-1-7, 2-16-2, 2-16-3 |
Wrong account, wrong role or wrong password |
| No | 1-1-11, 2-7-2, 2-7-3 version mismatch |
Software and Kilobaser data disagree. Contact support |
| No | 1-2-25 to 1-2-37 hardware faults |
Stop and tell the operator |
Two rules that save more trouble than the table.
Reading is always safe, writing rarely is. Every GET here can be repeated freely.
POST /processRuns/queue cannot: it has no idempotency key, so a retry after an
ambiguous failure gives you two oligos. Read the queue and compare ids instead.
POST /users is worse, because it deletes anything absent from the array you send — never
retry it with a stale array.
Do not clear errors automatically so that your client can keep going. An active error means the Kilobaser has a physical problem, and acknowledging it without fixing anything is how a bad run becomes a bad cartridge. Handle a specific code you understand, or stop and tell somebody.