GET/v1/me
The account and token making the request
Any valid token.
Answers: 200, 401, 403, 429, 503
Your projects over a REST API, circuits run headlessly on the same engine as the editor, and the tools to put both in CI or in your own editor.
Three ways in: plain HTTP, the CLI, or a GitHub Action that fails the build when the circuit misbehaves.
The CLI and the Action are not on npm or the Actions marketplace yet. Until they are, both live in the Mokxi repository, in packages/mokxi-cli and packages/simulate-action.
# Make a token at mokxi.com/account#developer, then:
export MOKXI_TOKEN=mkx_...
curl -s https://mokxi.com/api/v1/me \
-H "Authorization: Bearer $MOKXI_TOKEN"
# Run the built-in Blink for three simulated seconds and check pin 13
curl -s https://mokxi.com/api/v1/simulate \
-H "Authorization: Bearer $MOKXI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"example": "uno-blink",
"duration": "3s",
"assertions": [
{ "type": "pin-toggles", "pin": "uno1:13", "count": 6 },
{ "type": "pin-level", "pin": "uno1:13", "atMs": 250, "level": "high" }
]
}'# The CLI lives in packages/mokxi-cli in the Mokxi repository
mokxi login # approve it in the browser, no token to paste
mokxi projects ls
mokxi projects pull 3fQ9x2bLk7Hq blink
mokxi simulate 3fQ9x2bLk7Hq --for 5s \
--expect-serial "ready" --expect-toggles "uno1:13>=4"
echo $? # 0 passed, 1 failed, 2 usage, 3 API error
mokxi watch blink --open # push on save; the editor tab reruns# .github/workflows/circuit.yml
name: Circuit
on: [push]
jobs:
simulate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: arduino-cli compile -b arduino:avr:uno --output-dir build .
- uses: mokxi/simulate-action@v1
with:
token: ${{ secrets.MOKXI_TOKEN }}
project: 3fQ9x2bLk7Hq
firmware: build/sketch.ino.elf
duration: 5s
expect-serial: ready
expect-toggles: uno1:13>=4A personal access token in the Authorization header, on every request.
Make a token in Settings > Developer on your account page and send it as Authorization: Bearer mkx_.... It is shown once, when it is made; Mokxi keeps only a hash of it. A token carries only the scopes you give it, and revoking it stops it at once. The API never reads your browser session, so a web page cannot use your account through it.
| Scope | What it allows |
|---|---|
projects:read | Read your projects and their files |
projects:write | Create, change and delete your projects |
simulate | Run simulations |
Tools that should not ask anybody to paste a token (the CLI, editor plugins) use the device login: they ask for a short code, you approve it at mokxi.com/device while signed in, and the tool gets its own token, which then shows up in your list like any other.
POST /api/v1/auth/device { "clientName": "Mokxi for VS Code" }
-> { deviceCode, userCode: "BDFK-QRTW", verificationUri, interval: 5 }
Show userCode, open verificationUri (https://mokxi.com/device).
POST /api/v1/auth/token { "deviceCode": "..." } every 5 s
-> 400 authorization_pending ... then 200 { accessToken: "mkx_..." }Your circuit runs on Mokxi’s servers, on exactly the engine the editor runs in your browser.
POST /v1/simulate takes a saved project, a built-in example, a .mokxi.json document or a diagram with its files. It runs for the simulated time you ask for and answers with what every board printed, the pins you traced, and a pass or fail per assertion: text on the serial port, a pattern, a pin level at a moment, or how many times a pin changed in a window. Send Accept: text/event-stream to watch the serial output arrive as it is printed.
The server does not compile sketches. The editor compiles in your browser with its own toolchain, which is too big for a server request. So each board runs, in this order: an image you send (firmware), the image stored with the project (the editor stores one every time you press Run on a saved project, and PUT /v1/projects/{id}/firmware/{board} stores one from CI), an ELF uploaded into the diagram, or the shipped image of a built-in example you have not edited. A stored image only runs while the project’s files are still the ones it was built from. Anything else is a 422 that names the board.
Every board in the catalog runs server-side. Images can be ELF for every board, Intel HEX for AVR and Arm boards, UF2 for the Pico, the XIAO and the micro:bit, and a raw flash image for AVR. GET /v1/boards lists each board’s formats.
Per token, by the plan of the account that owns it, and a monthly allowance of simulated time per account.
| Plan | Requests a minute | Simulations an hour | Simulated time per run | Server time per run | Tokens | Simulation minutes a month |
|---|---|---|---|---|---|---|
| Free | 60 | 30 | 10 s | 5 s | 5 | 100 |
| Pro, Student, Teacher and Classroom | 600 | 600 | 60 s | 20 s | 25 | 2,000 |
Every answer to a request with a token carries RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset (seconds) and RateLimit-Policy. A simulation also counts against its own hourly window, reported in X-Simulations-Remaining. Past a limit the answer is 429 with Retry-After. The catalog routes (examples, boards, parts) work without a token at a lower limit.
Simulation minutes. Each account gets a monthly allowance of simulated time, counted across all of its tokens and reset at midnight UTC on the first of each month. Free includes 100 minutes a month. Pro and Student include 2,000 minutes. Teacher and Classroom include 2,000 minutes shared by the class: the teacher and the students in the teacher’s paid classes draw from one pool. Schools and universities can have more; talk to us at https://mokxi.com/schools/quote.
The allowance is checked when a run starts, so a run that starts under it always finishes. Once it is used up, POST /v1/simulate answers 402 with the code quota_exceeded and a message that says when it resets. Nothing is ever charged. Every simulate answer carries X-Quota-Limit-Minutes, X-Quota-Used-Minutes and X-Quota-Reset (an ISO 8601 time), and Settings > Developer shows the same numbers.
One shape for every failure, with a real status code.
{
"error": {
"code": "insufficient_scope",
"message": "this token does not have the simulate scope",
"requestId": "req_7kq2..."
}
}Branch on code, show message, and quote requestId (also in the X-Request-Id header) if you write to support.
| Status | Codes | Meaning |
|---|---|---|
| 400 | bad_body, bad_spec, bad_firmware, bad_path | The request could not be read, or asks for something impossible. |
| 401 | unauthorized | No token, or one that is wrong, revoked or expired. |
| 402 | plan_limit, quota_exceeded | The plan’s project cap is reached, or this month’s simulation minutes are used up. Nothing is ever charged. |
| 403 | insufficient_scope | The token does not carry the scope the route needs. |
| 404 | not_found | No such thing, or a private project that is not yours: the two look the same on purpose. |
| 412 | precondition_failed | An If-Match or If-None-Match did not hold: somebody else wrote first. |
| 413 | too_large | Over a size cap: 1.5 MB for a project, 1.5 MB for an image, 8 MB for a simulate request. |
| 422 | firmware_required, bad_circuit, unknown_pin | The request is sound but the circuit cannot run as asked. |
| 429 | rate_limited | Over the plan’s limit. Retry-After says how long to wait. |
| 503 | api_unavailable | The API is not switched on for this site yet. |
Keep the code in VS Code, Sublime or anything else, and the circuit in Mokxi.
Files by path. GET /v1/projects/{id}/files lists diagram.json and every board’s files as paths like uno1/sketch.ino, each with an ETag. Write one with PUT /v1/projects/{id}/files/{path} and send the ETag you last read as If-Match: if somebody changed it since, the answer is 412 and nothing is overwritten. If-None-Match: * creates a file only if it is not there.
Watch mode. GET /v1/projects/{id}/events is a Server-Sent Events stream that says updated after every write to the project, whoever made it. Open the project at /p/<id>?watch=1 and the editor tab reloads and reruns on every save; mokxi watch pushes each file as you save it.
A panel inside the editor. https://mokxi.com/embed/local?origin=<your webview origin> is the simulator with nothing around it. Put it in an iframe, wait for mokxi:ready, and post it the circuit; post it again on save. It only listens to its parent window, and only from the origin you named. An edited sketch with no image is compiled right there, by the same in-browser compiler the editor uses. No account and no cookies are involved; nothing is saved.
<iframe src="https://mokxi.com/embed/local?origin=vscode-webview://<id>"></iframe>
// The page says it is ready:
{ type: "mokxi:ready", version: 1 }
// Send it the circuit, again on every save:
{ type: "mokxi:load", diagram, files, firmware } // firmware optional, base64 by board
{ type: "mokxi:serial-input", board: "uno1", text: "hello\n" }
{ type: "mokxi:stop" }
// It answers with:
{ type: "mokxi:status", state: "building" | "running" | "stopped" | "error", message }
{ type: "mokxi:serial", board: "uno1", text: "..." }GET /v1/boards and GET /v1/parts are the catalog, for completions: every part type, its pins and its properties.
Every route, from the OpenAPI 3.1 document at /api/v1/openapi.json. Paths are under https://mokxi.com/api.
Download openapi.json for Postman, Insomnia or a client generator.
Who the token belongs to.
/v1/meThe account and token making the request
Any valid token.
Answers: 200, 401, 403, 429, 503
Your projects: list, read, create, change, delete, export.
/v1/projectsList projects
Your own projects by default, newest change first; scope=public lists everyone’s public ones. Paged with an opaque cursor: pass nextCursor back as cursor until it is null.
Scope: projects:read.
scope (query, mine | public)visibility (query, all | public | private) With scope=mine only.board (query, string) A board type from GET /v1/boards.q (query, string) Matches the project name.sort (query, latest | loved) loved with scope=public only.limit (query, integer)cursor (query, string)Answers: 200, 400, 401, 403, 429, 503
/v1/projectsCreate a project
From a diagram and files, from a .mokxi.json document, as a copy of a built-in (example), or as a fork of a project you can read (fork). Counts against your plan’s project cap (402 plan_limit). Private unless you say public: true.
Scope: projects:write.
Answers: 201, 400, 401, 402, 403, 404, 413, 429, 503
/v1/projects/{id}Get a project, with its diagram and files
Yours, or anybody’s public one. A private project that is not yours is a 404, the same as one that does not exist. The ETag header (also etag in the body) goes in If-Match on a later write; If-None-Match answers 304 when nothing changed.
Scope: projects:read.
id (path, required, string) The project id, as in /p/<id>.Answers: 200, 304, 401, 403, 404, 429, 503
/v1/projects/{id}Change a project
Any of name, description, public, pinned, and the body (diagram and files, or a document). Each change to the body is a checkpoint in the version history. Send If-Match to refuse the write when somebody else changed the project first (412).
Scope: projects:write.
id (path, required, string) The project id, as in /p/<id>.If-Match (header, string)Answers: 200, 401, 403, 404, 409, 412, 413, 429, 503
/v1/projects/{id}Delete a project
With its history, hearts and stored firmware. If-Match is honored.
Scope: projects:write.
id (path, required, string) The project id, as in /p/<id>.Answers: 200, 401, 403, 404, 429, 503
/v1/projects/{id}/exportExport as .mokxi.json
Exactly the file the editor’s Download JSON writes, with its files inside. POST /v1/projects with { "document": ... } imports it again.
Scope: projects:read.
id (path, required, string) The project id, as in /p/<id>.Answers: 200, 401, 403, 404, 429, 503
/v1/projects/{id}/eventsFollow changes to a project (Server-Sent Events)
A text/event-stream. The first event is state (the project as it is now); then updated after every write, whoever made it, gone if it is deleted or made private, and bye after ten minutes. EventSource reconnects by itself. mokxi watch and the editor’s watch mode (/p/<id>?watch=1) are built on it.
Scope: projects:read.
id (path, required, string) The project id, as in /p/<id>.Answers: 200, 401, 403, 404, 429, 503
A project’s files by path, with ETags for safe sync from an editor.
/v1/projects/{id}/filesList a project’s files
diagram.json and every board’s files, each with its size and ETag, and the project’s ETag.
Scope: projects:read.
id (path, required, string) The project id, as in /p/<id>.Answers: 200, 401, 403, 404, 429, 503
/v1/projects/{id}/files/{path}Read one file
The file’s text, with its ETag. If-None-Match answers 304.
Scope: projects:read.
id (path, required, string) The project id, as in /p/<id>.path (path, required, string) diagram.json, <board id>/<file> (like uno1/sketch.ino), or a bare file name when the project has one board or a loose sketch.Answers: 200, 304, 401, 403, 404, 429, 503
/v1/projects/{id}/files/{path}Write one file
The body is the file’s new text. If-Match: <etag> writes only over that version and If-None-Match: * only creates, so two editors saving at once get a 412 rather than a lost update. A new file on a board that has none starts that board’s project. Writing diagram.json replaces the diagram and leaves the files alone.
Scope: projects:write.
id (path, required, string) The project id, as in /p/<id>.path (path, required, string) diagram.json, <board id>/<file> (like uno1/sketch.ino), or a bare file name when the project has one board or a loose sketch.If-Match (header, string)If-None-Match (header, string)Answers: 200, 201, 400, 401, 403, 404, 412, 413, 429, 503
/v1/projects/{id}/files/{path}Delete one file
A board keeps at least one file (409 last_file), and diagram.json cannot be deleted.
Scope: projects:write.
id (path, required, string) The project id, as in /p/<id>.path (path, required, string) diagram.json, <board id>/<file> (like uno1/sketch.ino), or a bare file name when the project has one board or a loose sketch.If-Match (header, string)Answers: 200, 401, 403, 404, 409, 412, 429, 503
Built images stored with a project, for the simulator.
/v1/projects/{id}/firmwareList stored images
Which boards have an image stored, and whether each was built from the files as they are now (current). A stale image is never run.
Scope: projects:read.
id (path, required, string) The project id, as in /p/<id>.Answers: 200, 401, 403, 404, 429, 503
/v1/projects/{id}/firmware/{board}Store a built image for a board
The body is the image (ELF, Intel HEX, UF2, or a raw AVR image), at most 1.5 MB. X-Source-Hash is the SHA-256 of the files it was built from (see the /developers page for how it is computed); without it, the files as stored now are assumed.
Scope: projects:write.
id (path, required, string) The project id, as in /p/<id>.board (path, required, string) The board’s part id, like uno1.X-Source-Hash (header, string)Answers: 201, 401, 403, 404, 413, 429, 503
/v1/projects/{id}/firmware/{board}Forget a stored image
Scope: projects:write.
id (path, required, string) The project id, as in /p/<id>.board (path, required, string) The board’s part id, like uno1.Answers: 200, 401, 403, 404, 429, 503
Run a circuit headlessly.
/v1/simulateRun a circuit headlessly
Runs on the server, on the same wasm engine the editor uses, for the simulated time you ask (capped by your plan). Returns what the boards printed, the pins you traced, and a pass or fail for each assertion. passed is true only when every assertion passed and the run was not cut short.
Send Accept: text/event-stream to get Server-Sent Events instead: start, then serial as the boards print and progress now and then, then one result with the same body the JSON answer has.
Each run also counts against a per-hour simulation window, reported in X-Simulations-Limit, X-Simulations-Remaining and X-Simulations-Reset.
Each run's simulated time counts toward the account's monthly simulation minutes, reported in X-Quota-Limit-Minutes, X-Quota-Used-Minutes and X-Quota-Reset. The check is made when the run starts: a run that starts under the limit finishes even if it goes over. Once the month's minutes are used, the answer is 402 quota_exceeded until the first of next month (UTC). Nothing is ever charged.
Scope: simulate.
Answers: 200, 400, 401, 402, 403, 404, 413, 422, 429, 503
Built-in projects, boards and parts. No token needed.
/v1/examplesBuilt-in projects
Every project that ships with Mokxi. full=1 adds each one’s diagram and files. Fork one with POST /v1/projects { "example": "<slug>" }, or run it with POST /v1/simulate { "example": "<slug>" }.
No token needed.
board (query, string)full (query, 0 | 1)Answers: 200, 429
/v1/boardsBoards
Every board: pins, the firmware formats the simulator takes, whether it runs server-side, and where its sketches compile.
No token needed.
Answers: 200, 429
/v1/partsParts
The whole part catalog: every type, its pins and its properties. Stable, for completions in an editor.
No token needed.
Answers: 200, 429
The device login, for tools that should not ask you to paste a token.
/v1/auth/deviceStart a device login
Step one of the device login. Show the person userCode and send them to verificationUri (or verificationUriComplete); then poll POST /v1/auth/token. No token needed.
Answers: 200, 429
/v1/auth/tokenPoll a device login
Every interval seconds, until it answers 200 with the token (once). Until then it is a 400 whose code says why: authorization_pending, slow_down, access_denied or expired_token.
Answers: 200, 400