The Tron API
Your trons, called from your own code with a key: a message is a job, queued, which you read, follow, cancel or receive by webhook, with the files it produced and the shared folder of the tron.
The address and the key
Every route is under https://api.vibetron.ai/v1. A key (vbt_tk_…) is made in the site for one or more trons (POST /me/api-keys with type tron_api, or POST /trons/:id/api-keys), always saying when it expires (a date, a number of days, or never), and sent as Authorization: Bearer. It sees only what it did itself: its jobs, its sessions. A tron it does not cover is a 404, never a 403. GET /v1/trons is open to any valid key, and is the way to check one.
curl https://api.vibetron.ai/v1/trons \
-H "Authorization: Bearer vbt_tk_..."Scopes
A key carries only the scopes it was given, and a route it does not cover is a 403 scope_missing naming the scope. The complete list for a Tron API key: chat (send messages, read, follow and cancel its own jobs, see and decide the Ghost questions); sessions:read (list its sessions and read their messages); sessions:write (open a session, write into a chosen one); files:read (list and download the shared folder, the files of its jobs, the documents and the export); files:write (upload, move, make folders, delete, restore, add and remove documents); usage:read (what the trons it covers consumed and cost); trons:read (the .tron document); trons:write (change a tron from its document). An MCP key (vbt_pat_) has its own, longer list, for POST /mcp.
Sending a message
/v1/trons/:id/messagesScope chat. { message | inputs, nucleus_id?, session_id? }: queues a job and answers 202 with job_id, status, agent, nucleus_id, session_id, turn_id and poll. Without nucleus_id, the tron's first agent. session_id is one of this key's sessions and needs sessions:write; without it, the key's default session. 60 writes a minute per key./v1/tronsAny scope. The trons the key reaches, each with its agents as { agent_id, name }: agent_id is the nucleus_id to send.curl -X POST https://api.vibetron.ai/v1/trons/<tron_id>/messages \
-H "Authorization: Bearer vbt_tk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4711" \
-d '{ "message": "Summarise the open tickets" }'Attachments
The same route in multipart/form-data: message (or inputs, as JSON text), nucleus_id and session_id as fields, and the files as file parts — PDF and images, as from the chat. At most 10 files of 50 MB, 20 fields. Another field is a 400 unknown_field, a type the agent cannot read a 400 unsupported_file, a file too large a 413 file_too_large. The files reach the agent with the message and last as long as the job, unless the tron keeps attachments.
curl -X POST https://api.vibetron.ai/v1/trons/<tron_id>/messages \
-H "Authorization: Bearer vbt_tk_..." \
-F 'inputs={"city":"Turin"}' \
-F "[email protected]"A form: inputs
A tron whose interface is a form takes inputs, an object of field key to text, number or true/false, and the server writes the message from the saved template. Sending both message and inputs is a 400 message_or_inputs; a tron without a form a 400 no_form; a key the form does not have a 400 unknown_input; a value that is not text, number or boolean, or inputs that are not an object, a 400 invalid_inputs. A required field left empty, or a number out of range, is a 422 form_incomplete whose problems lists each field as { key, label, problem, builder }: problem says what is wrong ("is required", "must be at least 1"), builder is true when the fault is in the form itself (a choice with no options).
{ "inputs": { "city": "Turin", "days": 3 } }
422 { "error": "form_incomplete", "code": "form_incomplete",
"message": "City is required",
"problems": [{ "key": "city", "label": "City", "problem": "is required", "builder": false }] }AI marking: generated_by
A finished job says who produced it: GET /v1/jobs/:id carries generated_by = { ai: true, tron, agent, model } when its status is done, and the answer has the header X-Vibetron-Generated-By: ai; tron=<id>; model=<provider/model>. The files of a job (GET /v1/jobs/:id/files/*) and the files of the shared folder written by an agent (GET /v1/trons/:id/files/*) carry the same header, so a program that passes the result on can say it was generated by AI (AI Act art. 50).
{ "job_id": "…", "status": "done", "result": "…",
"generated_by": { "ai": true, "tron": "<tron_id>", "agent": "HEAD", "model": "anthropic/claude-sonnet-4-5" } }Idempotency-Key
Optional on POST /v1/trons/:id/messages: 1 to 255 printable ASCII characters, otherwise a 400 invalid_idempotency_key. It is unique per key and does not expire: the same key with the same message, agent and session answers 200 with the job it made the first time, and the header Idempotent-Replayed: true; with a different message, agent or session it is a 422 idempotency_key_reused, with that job_id. Other routes do not read it.
X-Vibetron-Credential
For an entry of the agent that is From caller: NAME=value, plain text, one header per entry, up to 20, a value at most 20000 characters; the name is the reference's name, whatever it is (GITHUB_TOKEN, GOOGLE_API_KEY). It lives for that job only: kept encrypted with the job and erased when the job ends. A missing one is a 422 credential_required with its name; one the agent does not take from the caller a 400 credential_not_used; a malformed one a 400 invalid_credential, the same name twice a 400 duplicate_credential, more than 20 a 400 too_many_credentials; a runtime that cannot take them a 409 runtime_update_needed (redeploy).
-H "X-Vibetron-Credential: GITHUB_TOKEN=ghp_..." \
-H "X-Vibetron-Credential: JIRA_TOKEN=..."The states of a job
queued: waiting for its turn, or to be retried (available_at says when). running: handed to the agent. awaiting_decision: a Ghost check outvoted a step and asked; actions lists the questions, and POST /v1/actions/:actionId/decide answers — agent or majority puts the job back to queued with a new turn_id, stop ends it failed, and so does nobody deciding before the deadline. done: finished, with result. failed: finished with error, after its attempts (3 by default) or a stop. cancelled: by POST /v1/jobs/:jobId/cancel, from queued, running or awaiting_decision. done, failed and cancelled are final. A job that finds too many of your trons awake waits in queued; it does not fail.
The job
job_id, status, tron_id, agent (its label now), nucleus_id, session_id, turn_id, created_at, started_at, finished_at, attempts and max_attempts. Then, when they apply: available_at (queued), last_error (the latest failure, unless done), result (the answer, a string, when done), error (a string, when failed), actions (awaiting_decision), attachments (what was sent: filename, mime_type, size_bytes), files (what the agent produced: name, size and a ready download path) and webhook (status, attempts, delivered_at). There is no cost in the job: GET /v1/trons/:id/usage says it. Until 31 December 2026 an answer also carries the older Italian keys beside the English ones, with the headers Deprecation and Sunset; read the English ones.
/v1/jobs/:jobIdScope chat. The job, below. Only a job of this key./v1/jobsScope chat. This key's jobs, newest first: ?limit= (20, at most 100), ?status= one of the states. { jobs, shown, total }./v1/jobs/:jobId/cancelScope chat. A queued job never starts; a running one is stopped (runtime 0.21.0 and later; on an older one the model finishes and its tokens are charged, and the answer says so in warning). 409 if it is already final./v1/jobs/:jobId/files/*Scope files:read. A file the job produced, by its name in files[]; read from disk, it does not wake the tron.{
"job_id": "5b0c…", "status": "done", "tron_id": "9e1f…",
"agent": "Writer", "nucleus_id": "c2d4…", "session_id": "71aa…", "turn_id": "e03b…",
"created_at": "2026-11-05T09:12:03Z", "started_at": "2026-11-05T09:12:04Z",
"finished_at": "2026-11-05T09:12:41Z", "attempts": 1, "max_attempts": 3,
"result": "Three tickets are open: …",
"files": [{ "name": "summary.md", "size": 2048,
"download": "/v1/jobs/5b0c…/files/summary.md" }]
}Following a job
The stream carries the state of the job, not the text as it is written nor what each agent does: that is the site's chat stream and Live view, with a session (see Talking to a tron). A key reads the turn afterwards from its session (GET /v1/trons/:id/sessions/:sessionId/messages).
/v1/jobs/:jobId/streamScope chat. Server-sent events, each data: a JSON object with status: one when it opens, one at every change, a heartbeat every 20 seconds ({ status, heartbeat: true }), and a last one with result or error when the job is final, then the stream closes.Webhooks
A Tron API key can have a webhook: PUT /me/api-keys/:id/callback { url }, from the site, an https address on a public network; the answer shows the signing secret (whsec_…) once. DELETE removes it. When a job of that key ends the API POSTs { type: "job.finished", job_id, status, result or error, files, turn_report… }, and when a job stops at a Ghost question { type: "ghost.outvoted", actions }. Signed as Standard Webhooks: webhook-id (the job id, the same on every retry), webhook-timestamp and webhook-signature (v1, HMAC-SHA256 of id.timestamp.body). Up to 3 retries with backoff; a 4xx other than 408 and 429 is final, and no redirect is followed. Only the outcome is sent, never the stream; there are no webhooks for other events.
The Ghost
/v1/trons/:id/actionsScope chat. The Ghost questions of the tron: ?status= (proposed, decided, executed, stopped, expired, all), ?limit=./v1/actions/:actionId/decideScope chat. { decision: agent | majority | stop, note? }. The answer carries the job to follow when it resumes.Sessions
/v1/trons/:id/sessionsScope sessions:read. This key's sessions on the tron; nobody else's./v1/trons/:id/sessionsScope sessions:write. { title? }: opens one, 201./v1/trons/:id/sessions/:sessionId/messagesScope sessions:read. Its messages: ?limit=, ?before=.Usage
/v1/trons/:id/usageScope usage:read. What the tron consumed and cost: ?from=&to= or ?since_days=, ?currency=. The key of the owner sees all of it, an assignee's key only its own (view says which).