Tovan API
Tovan designs an electronic device from a plain-words brief (the circuit board, a case and the code) and has it made. This API lets a script or an AI agent do that for a person, with that person's account. The same functions are also an MCP server at https://www.tovan.ai/api/mcp; see Tovan for AI agents.
Base URL: https://www.tovan.ai/api/v1
Every request and response body is JSON, except the board picture (PNG).
Authentication
Send one of these in the Authorization header:
- An API key:
Authorization: Bearer tvn_…. Create one in Tovan: account menu, then Keys. It is shown once. Revoke it from the same place. - An OAuth access token from Tovan's sign-in, which MCP clients get for you. The server's metadata is at
https://www.tovan.ai/.well-known/oauth-protected-resource/api/mcp.
Everything runs as that person: their boards, their build credit, their limits. A guest session cannot use the API (account_required).
Errors
Every error has the same shape: a code, a sentence, and what to do next.
{
"error": "needs_credit",
"message": "This account is out of build credit.",
"next": "Send the person to https://www.tovan.ai/?projectId=…&topup=1 to add credit, then call again."
}| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | No key or token, or it is no longer valid. |
account_required | 403 | The credential belongs to a guest session. |
not_found | 404 | No board or order with that id on this account. |
invalid_input | 400 | The body did not match; message says which field. |
needs_credit | 402 | Out of build credit; next has the link where the person adds more. |
rate_limited | 429 | Too many calls; wait and try again. |
turn_running | 409 | The board is still working; check its status and try again when it stops. |
needs_person | 403 | Only the person can do this; next has the link to send them. |
blocked | 403 | We do not make this kind of device. |
Polling
A build takes minutes, sometimes more than half an hour. No call waits for it:
POST /boardsstarts the build and returns at once.GET /boards/:id/statusevery 30 to 60 seconds.- On
waiting_for_answer, answerquestionwithPOST /boards/:id/messages, then keep polling.nextsays when the answer is a confirm. - On
done, read the board withGET /boards/:id. - On
needs_credit, send the person tonext. Onstopped, send a message to carry on.
Endpoints
| Method and path | Does |
|---|---|
GET /boards | List the person's boards |
GET /boards/:id | Everything about one board |
GET /boards/:id/picture | The board picture (PNG) |
POST /boards | Start a new board from a brief |
GET /boards/:id/status | Where the build is |
POST /boards/:id/messages | Answer a question, or ask for a change |
POST /boards/:id/order | Price the board and get the approve link |
GET /orders/:id | Where an order is, after the person paid |
GET /boards
Query: limit (default 20).
[
{
"board_id": "0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10",
"name": "Plant moisture sensor",
"summary": "Battery soil moisture sensor that reports over Wi-Fi.",
"updated_at": "2026-10-30T18:04:11Z",
"state": "done",
"picture_url": "https://www.tovan.ai/board-picture/0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10/….png",
"editor_url": "https://www.tovan.ai/?projectId=0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10"
}
]state is null for a board that has never had a build.
GET /boards/:id
Right after a board changes, parts_pending is true while its parts list and picture are prepared, usually for under a minute. Call again to get them.
{
"board_id": "0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10",
"name": "Plant moisture sensor",
"summary": "Battery soil moisture sensor that reports over Wi-Fi.",
"state": "done",
"recap": "The board is laid out and every check passed.",
"question": null,
"picture_url": "https://www.tovan.ai/api/v1/boards/0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10/picture",
"requirements": [
{ "text": "Runs 6 months on 2 AA batteries", "verdict": "met", "why": "Average draw 0.21 mA, about 11 months." },
{ "text": "Fits a 40 x 80 mm case", "verdict": "met", "why": null }
],
"parts": [
{ "reference": "U1", "name": "Wi-Fi microcontroller", "part_number": "ESP32-C3-MINI-1", "quantity": 1, "unit_price_usd": 1.9, "in_stock": true }
],
"estimated_parts_cost_usd": 6.4,
"documents": [
{ "kind": "bring_up_guide", "title": "Bring-up guide", "url": "https://www.tovan.ai/…" }
],
"cost_so_far_usd": 0.82,
"editor_url": "https://www.tovan.ai/?projectId=0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10",
"updated_at": "2026-10-30T18:04:11Z"
}verdict is one of met, not_met, open, unknown. open means not checked on this version of the board. When the board changed after a check, why says what that check last found; it does not count until the board is checked again.
GET /boards/:id/picture
Returns image/png: a 3D picture of the board as it is now.
POST /boards
Body:
{
"brief": "A battery-powered soil moisture sensor for house plants. It reads moisture every 10 minutes and sends it over Wi-Fi to a phone app. Runs on 2 AA batteries for at least 6 months. Must fit in a 40 x 80 mm case. Indoors. We want 3.",
"name": "Plant moisture sensor",
"origin": "api"
}| Field | Type | |
|---|---|---|
brief | string, 10 to 20,000 characters | Required. What it does, power, size and mounting, where it lives, how many, any chosen parts or code repository. |
name | string, up to 120 characters | Optional. A name is chosen from the brief if left out. |
origin | string, up to 60 characters | Optional. Who is calling, for example api or mcp:my-agent. |
Returns a ticket:
{
"board_id": "0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10",
"session_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"state": "working",
"recap": "Started.",
"editor_url": "https://www.tovan.ai/?projectId=0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10"
}GET /boards/:id/status
{
"board_id": "0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10",
"state": "waiting_for_answer",
"recap": "Picked the parts and needs one answer before laying out the board.",
"question": "Should the sensor also measure temperature?",
"cost_so_far_usd": 0.31,
"started_ago_ms": 412000,
"editor_url": "https://www.tovan.ai/?projectId=0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10",
"next": null
}state | Meaning |
|---|---|
working | Building. Check again in 30 to 60 seconds. |
waiting_for_answer | question needs an answer. |
needs_credit | Out of build credit; next is where the person adds more. |
done | Finished this round. Read the board, or ask for a change. |
stopped | Stopped before finishing. Send a message to carry on. |
recap is always one sentence.
POST /boards/:id/messages
Body:
{ "message": "No temperature, moisture only." }If the board is waiting for an answer, this answers it. Otherwise it asks for a change ("make the case 5 mm shorter"). Returns the status, as above, usually working. Returns turn_running while a build is still going.
When the status asks whether the requirements look right, show them to the person. Once they agree, send "confirm_requirements": true with the message Looks right, build it. Tovan then runs its final check.
POST /boards/:id/order
Prices the board and returns a link for the person to approve and pay. The API never takes payment.
Body:
{ "quantity": 3, "scope": "assembled" }| Field | ||
|---|---|---|
quantity | 1 to 100, default 1 | Boards are made in runs of 5, 10, 15, 20, 25, 30, 50, 75 or 100. A count in between is priced at the next run up. |
scope | assembled (default) or boards_only | assembled means boards with parts soldered on; boards_only means bare boards and a list of the parts. |
Returns:
{
"board_id": "0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10",
"approval_id": "5e0c9a7b-2d1f-4e3a-9b8c-7d6e5f4a3b2c",
"approve_url": "https://www.tovan.ai/approve/…",
"quantity": 5,
"requested_quantity": 3,
"quantity_note": "Boards are made in runs of 5, 10, 15, 20, 25, 30, 50, 75 or 100, so 3 became 5.",
"lines": [
{ "label": "5 boards with the parts on", "amount_usd": 41.2 },
{ "label": "Order fee", "amount_usd": 99 },
{ "label": "Shipping", "amount_usd": 12.5 }
],
"total_usd": 152.7,
"lead_time": "2 to 3 weeks",
"saved_card": true,
"expires_at": "2026-11-01T18:10:00Z",
"next": "Send the person the approve_url. They review the board and pay with one tap."
}The link lasts 48 hours. The person sees the picture, the requirements, the price and pays. Every order is checked by an engineer on our team before it is made. Returns blocked if we do not make this kind of device.
GET /orders/:id
{
"order_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"board_id": "0b6f6c55-3c0a-4c1e-9d0e-2f1f6f0b7a10",
"state": "engineer_check",
"engineer_check": "In progress",
"tracking_url": null,
"recap": "Paid. Our engineer is checking the design before it is made."
}state is one of awaiting_approval, paid, engineer_check, fix_sent, in_production, shipped, delivered, refunded, cancelled.
Help
Email austin@tovan.ai.