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."
}
CodeHTTPMeaning
unauthorized401No key or token, or it is no longer valid.
account_required403The credential belongs to a guest session.
not_found404No board or order with that id on this account.
invalid_input400The body did not match; message says which field.
needs_credit402Out of build credit; next has the link where the person adds more.
rate_limited429Too many calls; wait and try again.
turn_running409The board is still working; check its status and try again when it stops.
needs_person403Only the person can do this; next has the link to send them.
blocked403We do not make this kind of device.

Polling

A build takes minutes, sometimes more than half an hour. No call waits for it:

  1. POST /boards starts the build and returns at once.
  2. GET /boards/:id/status every 30 to 60 seconds.
  3. On waiting_for_answer, answer question with POST /boards/:id/messages, then keep polling. next says when the answer is a confirm.
  4. On done, read the board with GET /boards/:id.
  5. On needs_credit, send the person to next. On stopped, send a message to carry on.

Endpoints

Method and pathDoes
GET /boardsList the person's boards
GET /boards/:idEverything about one board
GET /boards/:id/pictureThe board picture (PNG)
POST /boardsStart a new board from a brief
GET /boards/:id/statusWhere the build is
POST /boards/:id/messagesAnswer a question, or ask for a change
POST /boards/:id/orderPrice the board and get the approve link
GET /orders/:idWhere 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"
}
FieldType
briefstring, 10 to 20,000 charactersRequired. What it does, power, size and mounting, where it lives, how many, any chosen parts or code repository.
namestring, up to 120 charactersOptional. A name is chosen from the brief if left out.
originstring, up to 60 charactersOptional. 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
}
stateMeaning
workingBuilding. Check again in 30 to 60 seconds.
waiting_for_answerquestion needs an answer.
needs_creditOut of build credit; next is where the person adds more.
doneFinished this round. Read the board, or ask for a change.
stoppedStopped 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
quantity1 to 100, default 1Boards 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.
scopeassembled (default) or boards_onlyassembled 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.