Quick start
Sign in, then create a key in Account → API keys. Keys start with jmk_ and are shown once: copy it into your environment, never into a file you commit.
export TAO1_API_KEY=jmk_...
Then send a chat completion. The request and response shapes are OpenAI's.
curl https://tao1.ai/v1/chat/completions \
-H "Authorization: Bearer $TAO1_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9f1c2a7e-quiz-001" \
-d '{
"model": "tao-1",
"messages": [
{"role": "user", "content": "Write a 20-question IQ quiz as a single HTML file."}
],
"metadata": {"session_id": "6f3b7c2e-0a4b-4c9d-8e1f-2a3b4c5d6e7f"}
}'
Or with the OpenAI SDKs: set the base URL and the key, keep the rest of your code. Python (pip install openai):
import os
from openai import OpenAI
client = OpenAI(base_url="https://tao1.ai/v1", api_key=os.environ["TAO1_API_KEY"])
reply = client.chat.completions.create(
model="tao-1",
messages=[{"role": "user", "content": "Write a 20-question IQ quiz as a single HTML file."}],
)
print(reply.choices[0].message.content)
JavaScript (npm install openai, as an ES module):
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://tao1.ai/v1", apiKey: process.env.TAO1_API_KEY });
const reply = await client.chat.completions.create({
model: "tao-1",
messages: [{ role: "user", content: "Write a 20-question IQ quiz as a single HTML file." }],
});
console.log(reply.choices[0].message.content);
To stream, add stream: true (Python: stream=True) and read the chunks as you would from OpenAI.
stream: true for server-sent events; the charge for each turn is settled from the request's actual cost when the stream ends. Tools that stream by default (Kilo Code, Cline) work with their default settings; see below. Turns longer than about 3 minutes have not been tested through the edge yet.Authentication
Every request carries Authorization: Bearer <key>. A key belongs to one account; its requests are billed to that account's credits. A missing or unknown key answers 401.
The key is shown once when you create it. If you lose it, create another in Account → API keys.
Models
The model field is a dial, not a fixed engine: TAO-1 routes each request to the cell and harness that fit it, and the dial says what to optimise. metadata.objective overrides the dial for one request. An unknown model answers 404.
| Model id | Dial | Use it when | Price per M tokens (in / cached / out) |
|---|---|---|---|
tao-1 | balanced (default) | Most requests. | $0.50 / $0.15 / $7.50 |
tao-1-fast | cost | Our fastest, lowest-cost dial: cheap, quick answers; high-volume calls. | $0.25 / $0.075 / $4.00 |
tao-1-best | quality | The answer matters more than the price or the wait. Coming soon. | Price to be announced |
GET /v1/models lists the same three ids.
Credits and billing
A new account carries a $5 free grant. Each request places a $0.05 hold on your balance, then settles to the request's metered cost when TAO-1's cost meter is read back; the difference is released. Long calls settle the same way: TAO-1 reports the request's final all-in cost (the model calls plus the routing classifier) when the answer ends, and that is what you pay. If it costs more than the hold, the real cost is charged. Only when no cost comes back is the hold charged flat at $0.05.
When the balance cannot cover the hold, the request answers 402 with insufficient_credits and nothing is sent to TAO-1. Balance, holds and settled usage are on the account page.
POST/v1/chat/completions
An OpenAI-compatible chat completion routed by TAO-1. The fields below are the complete inventory the API accepts; a key not listed here is not read.
Request body
| Field | Type | Meaning |
|---|---|---|
model | string | One of tao-1, tao-1-fast; default tao-1. The dial (see Models). Unknown → 404. |
messagesrequired | array | OpenAI messages: each an object with a string role and a content that is a string, null, or an array of part objects. Empty or malformed → 400. |
stream | boolean | Default false. With true: SSE chunks as they arrive, a : keepalive comment while TAO-1 works, the last data chunk carries finish_reason, then a : jtao charge_micro=… meter=route_event comment with the settled charge before data: [DONE]. |
stream_options | object | OpenAI shape; include_usage: true appends a final chunk with the billed token counts (streaming only). Non-streaming responses always carry usage. |
tools | array | OpenAI tool definitions; an empty array means no tools. |
tool_choice | string | object | OpenAI tool_choice, passed through when tools are given. |
routing_generations | integer 1..8 | Default 2. How many generations of subtasks go through the classifier and router: 1 = only the request, 2 = its children, 3 = their children. Alias metadata.routing_generations; a non-integer, an out-of-range value, or a body value that disagrees with its alias → 400 with param: routing_generations. See Routing generations. |
metadata | object | TAO-1-specific keys; see below. |
metadata
| Key | Type | Meaning |
|---|---|---|
session_id | string | The client session; usage rows and the workspace join on it. A repeated last user turn within 120 s of the previous request in the same session is treated as a regenerate of it. |
objective | string | cost | balanced | quality. Overrides the model's dial for this request. |
result_format | string | files | bluetao_project. Asks for a project build: files returns james_build with the written files; bluetao_project the BlueTAO project shape. Both make the build harness plan. |
routing_generations | integer 1..8 | Alias of the body field; must agree with it. |
regenerate_of | string | The request id the client rejected; the regenerate policy avoids its path. |
privacy | boolean | Marks the request private: only enclave (TEE) providers may serve it; refused when none is available. |
persona_name | string ≤ 40 | The assistant's name in the persona rules. Default TAO-1. |
task_kind | string | A caller's task hint passed to the build harness. |
human_progresscoming | boolean | Not yet available on the API. Planned: on streamed coding requests, plain-language progress updates in the assistant's own content. Not read by the deployed API today. See Human-facing progress. |
Debug keys only. Keys the operator also lists as debug keys may send metadata.debug, metadata.force_harness (build), metadata.force_cell (provider/model, ≤ 80 chars) and metadata.force_classifier_fallback, and receive the early x-james-route header (routing decision as JSON). Account keys from jtao.ai are not debug keys; these fields are documented so the inventory is complete.
Headers
| Header | Direction | Meaning |
|---|---|---|
Authorizationrequired | request | Bearer jmk_… |
Content-Type | request | application/json |
Idempotency-Key | request | Optional. When sent, a repeat of the same key answers 409 instead of running the request twice. Use one key per logical request (a UUID is fine). |
Response
The OpenAI fields: id, object, created, model, choices[].message.content, choices[].message.reasoning_content, choices[].message.tool_calls, choices[].finish_reason, usage. Plus the TAO-1 fields:
| Field | Meaning |
|---|---|
choices[].finish_reason | stop | tool_calls | partial | no_output. partial = the answer was cut by a budget; no_output = nothing served (the message is in error). |
james_build | When metadata.result_format is files: the build result — plan, files written, checks. |
james_keepalive | On non-streamed long requests: { "bytes", "every_ms" } of the keep-alive padding that preceded the body. |
The shape of a non-streamed response, showing where the TAO-1 fields sit:
{
"id": "…", "object": "chat.completion", "created": …, "model": "tao-1",
"choices": [
{ "index": 0,
"message": { "role": "assistant", "content": "…", "reasoning_content": null, "tool_calls": null },
"finish_reason": "stop" }
],
"usage": { "prompt_tokens": …, "completion_tokens": …, "total_tokens": … }
}
Images
Send images as OpenAI image_url content parts in a user message. The url can be a data:image/…;base64,… URL or an https URL. An image request goes to an open-weights vision model.
{"model":"tao-1","messages":[{"role":"user","content":[
{"type":"text","text":"What's wrong with this UI?"},
{"type":"image_url","image_url":{"url":"data:image/png;base64,iVBORw0…"}}]}]}
Limits: PNG, JPEG, WebP or GIF (the first frame); keep each image under about 1.5 MB and the whole request under 4 MB. Downscale large screenshots so the long side is at most 2048 px. On jtao.ai the chat does this for you, up to 4 images per message. Images are sent to the model for that request only; neither jtao.ai nor TAO-1 stores them. For documents, see Files.
Images and audio in responses
Ask for one in plain words, for example Generate an image of a red bicycle leaning against a blue wall. The answer comes back as a normal chat completion: the file is a link inside choices[0].message.content, followed by the prompt that was used. One image per answer; it usually takes 8 to 11 seconds.
{"model":"tao-1","messages":[{"role":"user","content":"Generate an image of a red bicycle leaning against a blue wall."}]}
"content": "\n\n_Prompt: A weathered red vintage bicycle … photorealistic style._"
Images are PNG files with a public, unguessable address. Songs work the same way: the content holds an <audio controls src="https://….mp3"></audio> element and the prompt. Parse the first https:// address in the content, or render the Markdown or HTML as it is. Download the file if you need to keep it: we do not promise how long an address stays up. The usage field counts the text tokens only.
Say what you want to see ("Generate an image of …", "Make a picture of …"); a vague one-word ask can be answered in text instead.
Live search
When a question needs current information, TAO-1 searches the web and public posts on X by itself; there is no parameter to set. The answer says how fresh what it found is, and says so plainly when a source could not be reached or had little to show. The search results are part of the prompt, so they are billed as normal input tokens (they show up in usage.prompt_tokens), at the same per-key limits as any other request.
Files
TAO-1 reads PDF, EPUB, Word (DOCX), PowerPoint (PPTX), Excel (XLSX), text and code files. Only the text is read: the extractor keeps the page, chapter, slide and sheet markers ([p. 12], [ch. 3: Title], [slide 4], [sheet: Q3]) so answers can cite them. A file's text is given to the model as data: instructions written inside a file are not followed. Scanned PDFs without a text layer, and old .doc/.xls/.ppt files, are refused; save them as PDF, DOCX, XLSX or PPTX first.
A long document is read in full by a long-context model. Past about 3.5 MB of text (roughly 800k tokens), only the passages most relevant to your question are sent, with their markers.
Small files: inline
Send an OpenAI file content part with the file as a base64 data URL. The whole request must stay under about 3 MB.
{"model":"tao-1","messages":[{"role":"user","content":[
{"type":"file","file":{"filename":"memo.pdf","file_data":"data:application/pdf;base64,JVBERi0x…"}},
{"type":"text","text":"What does the memo say about revenue?"}]}]}
POST/v1/files
For files up to 50 MB, upload once and refer to the file by id. The shape is OpenAI's: multipart/form-data with a file field and an optional purpose (default user_data).
curl https://tao1.ai/v1/files \
-H "Authorization: Bearer $TAO1_API_KEY" \
-F purpose=user_data \
-F file=@moby-dick.epub
{
"id": "file-311e4c42fa1361c05c048d81",
"object": "file",
"bytes": 726981,
"created_at": 1790604773,
"filename": "moby-dick.epub",
"purpose": "user_data",
"status": "processed",
"tao1": { "kind": "epub", "chapters": 147, "tokens": 319217, "expires_after_unused_days": 30 }
}
Then use it in any chat request by file_id, as many times as you like:
{"model":"tao-1","messages":[{"role":"user","content":[
{"type":"file","file":{"file_id":"file-311e4c42fa1361c05c048d81"}},
{"type":"text","text":"Which ship picks up Ishmael at the end?"}]}]}
A raw body works too: POST /v1/files?filename=report.pdf with the bytes as application/octet-stream. Up to 10 files per chat request.
| Endpoint | Meaning |
|---|---|
POST /v1/files | Upload a file (≤ 50 MB). Answers the file object. |
GET /v1/files | Your files, newest first ({"object":"list","data":[…]}). |
GET /v1/files/{id} | One file object. |
GET /v1/files/{id}/content | The extracted text, as text/plain. The original file is not kept, so it cannot be downloaded. |
DELETE /v1/files/{id} | Deletes the file's text now. Answers {"id":…,"object":"file","deleted":true}. |
What is kept, and for how long. The uploaded file itself is never stored: it is read, its text is extracted, and only that text is kept, compressed, in your account. A file is deleted automatically when it has not been used in a chat request for 30 days, and with your account. Files are visible only to keys of the account that uploaded them; another account's file_id answers 404. Uploads are free; the chat requests that use a file are billed as usual, and a long document makes a larger request.
Limits: 50 MB and 2,000 pages per file; 30 uploads per account per hour (429 beyond). Text past 4 million characters (about 1M tokens) is cut, and the file object says "truncated": true.
Routing generations
routing_generations is an optional integer, 1–8, default 2. Generation 1 is the original request; generation 2 is its immediate children; generation 3 is their children. metadata.routing_generations is also accepted; conflicting values or invalid types return 400.
{"model":"tao-1","routing_generations":2,"messages":[{"role":"user","content":"Build a quiz app"}],"metadata":{"result_format":"files"}}
This limits fresh classification and routing; it does not force decomposition. Small briefs keep the single-call build path. At 1, internal children keep the previous coding router. At 2, immediate children use the main task classifier and task-specific model, provider, settings and harness rules; deeper descendants keep the old coding path. Raising the value permits fresh routing at additional existing levels, without raising the separate build depth, node or time limits.
The classifier sees each child's objective; execution also sees its required artifact format, interfaces and dependency outputs. The parent owns file writes and acceptance checks. Child sessions are ephemeral and cannot overwrite parent state. A classified child does not start a separate build or receive a new overall time budget.
Only the root request contributes to session billing totals; classifier usage is recorded separately and included in routed-child cost. A passed structural check is not a quality score.
Settings you can change
TAO-1 has no settings you are stuck with. Every fixed choice inside a request, such as which model leads, which model follows, how long TAO-1 waits before starting a second model, or how many drafts it writes, is either learned from measured calls on each model, or settable on your request, and usually both.
If you set nothing, you get the default, which is today's behaviour, tuned from measurement. A value you set applies to that one request and wins over the learned value. Each setting is accepted at the top level or under metadata; a value outside its range, a wrong type, or two places that disagree returns 400. The response's tao1 block records the value the request used.
You can lower a limit, never raise it past your account's limits. Billing, spend caps, rate limits, authentication and privacy routing are not settable.
Live now
| Setting | Type and range | Default |
|---|---|---|
routing_generations | integer, 1–8 | 2 (see Routing generations) |
leader_ai | a model id the provider serves, as provider/model | empty: today's measured leader for the task |
follower_ai | a model id the provider serves, as provider/model | empty: today's measured follower |
race_start | seconds, 0–1000; 0 starts the leader at once; a value past the request's deadline is clamped to it | empty: learned per model; 150 for Kimi K3 |
codevote_draws | integer, 1–5: the number of parallel drafts on checkable coding problems | empty: the table value, 3 today |
wall_window_s | seconds, more than 0 and at most 1000, clamped to the request's deadline: how long TAO-1 waits for output before it treats a lane as walled; a reasoning model that is still thinking is not cut off on tool turns | empty: the learned window for that model, capped by the deadline |
intent_followup | boolean: after a complete answer to a factual, price or advice question where knowing your purpose would change the best answer, TAO-1 ends with one short question offering two or three likely purposes; never on tool, code or design turns, on requests with output constraints, or when you already said why you ask | empty: off, and the reply is exactly what it is today |
small_brief_tags | words separated by spaces: a build request that contains any of them is not a small brief and is planned in steps; a request with none of them, at most four named files, at most four bullets and under 1,200 characters is built in one shot. Matched whole words, any case | empty: the deployment's SMALL_BRIEF_TAGS list, else the built-in list game renderer dashboard frontend backend websocket real-time realtime multiplayer voxel 3d webgl canvas strategy |
leader_ai and follower_ai are accepted at the top level (aliases LEADER_AI / FOLLOWER_AI) or under metadata; a top-level and a metadata value that disagree, or a model the provider does not serve, return 400 with param naming the setting. With both absent the behaviour is unchanged. The response's tao1.cell_params records the values the request used; when a cell was swapped in, tao1.cell_substitution (and the x-james-route header) carry {from, to, param}.
curl https://tao1.ai/v1/chat/completions \
-H "Authorization: Bearer $TAO1_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"tao-1","leader_ai":"engy/glm-5.3","messages":[{"role":"user","content":"Build a quiz app"}]}'
curl https://tao1.ai/v1/chat/completions \
-H "Authorization: Bearer $TAO1_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"tao-1","follower_ai":"engy/qwen3.8-27b","messages":[{"role":"user","content":"Build a quiz app"}]}'
race_start, codevote_draws and wall_window_s are accepted at the top level or under metadata (metadata.race_start, metadata.codevote_draws, metadata.wall_window_s; wall_window_s also as WALL_WINDOW_S); a bad value, or a top-level and a metadata value that disagree, returns 400 with param naming the setting. The response's tao1 block records the values the request used (race.backup_at_reason: race_start_param, draws_source: request, wall_clock.window_source: request). A dead follower lane still starts the leader at once, whatever race_start says.
curl https://tao1.ai/v1/chat/completions \
-H "Authorization: Bearer $TAO1_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"tao-1","race_start":0,"messages":[{"role":"user","content":"Build a quiz app"}]}'
curl https://tao1.ai/v1/chat/completions \
-H "Authorization: Bearer $TAO1_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"tao-1","codevote_draws":5,"messages":[{"role":"user","content":"Write a function that parses ISO-8601 durations, with tests"}]}'
curl https://tao1.ai/v1/chat/completions \
-H "Authorization: Bearer $TAO1_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"tao-1","wall_window_s":300,"messages":[{"role":"user","content":"Build a quiz app"}]}'
intent_followup is accepted at the top level or as metadata.intent_followup (JSON true/false or the strings "true"/"false"); anything else, or a top-level and a metadata value that disagree, returns 400 with param naming it. The response's tao1.intent_followup block records {on, source, fired, reason}: fired is true when the question was asked for, and reason says why it was not (tools, code, constraints, purpose_stated, …).
curl https://tao1.ai/v1/chat/completions \
-H "Authorization: Bearer $TAO1_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"tao-1","intent_followup":true,"messages":[{"role":"user","content":"What is the price of copper?"}]}'
small_brief_tags is accepted at the top level (alias SMALL_BRIEF_TAGS) or as metadata.small_brief_tags; a value that is not a string, or a top-level and a metadata value that disagree, returns 400 with param naming it. An empty value is the same as leaving it out. It changes only how a build request is sized; every other kind of request is unchanged. The deployment can also set JAMES_SMALL_BRIEF_RULE=word_and_signal, under which a listed word only makes a brief large when the brief is also over 600 characters or names three or more files. The build row records the list's source (param, env or default), the listed words it found, and the rule beside the size decision.
curl https://tao1.ai/v1/chat/completions \
-H "Authorization: Bearer $TAO1_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"tao-1","small_brief_tags":"game dashboard multiplayer","messages":[{"role":"user","content":"Build an IQ quiz app"}]}'
Nothing is waiting: every setting on this page is live. A setting added to TAO-1 later appears here the day it goes live.
A model with no measurements yet runs on the Kimi K3 settings until TAO-1 has measured it.
Human-facing progress coming
metadata.human_progress today and returns no tao1.human_progress field. This page will say when it is on.Planned behaviour: coding workspaces will be able to set metadata.human_progress: true on streamed requests. TAO-1 will add concise, plain-language progress guidance to the model, and the resulting updates will arrive as ordinary streamed assistant content alongside the answer and tool calls: the user's goal and first step, meaningful findings and decisions, and what changed or comes next, with no private chain of thought, no repeated generic status labels, no commands in progress messages and no unverified claims. It will use the existing model call; there is no separate progress request. Explicit false will disable it; JTAO Code sessions will have it on by default. The response will confirm it with a tao1.human_progress field.
Long requests
- The request total is 780 s. Every long-running budget inside a request ends there. A non-streamed call that reaches it answers
504with{ "error": { "type": "no_output" } }; a streamed call ends with a final chunk whosefinish_reasonisno_output(nothing produced) orpartial(something was). - The platform ceiling is 800 s. A function killed at the ceiling cannot answer: a non-streamed request then ends as an HTTP
200whose body is only keep-alive bytes (spaces) and no JSON. The 780 s total exists so this does not happen; if it ever does, treat a 200 with a non-JSON body as a transport failure and retry. - Keep-alive. Non-streamed coding responses carry
james_keepalive: { bytes, every_ms }so a client can tell keep-alive padding from the body. When streaming is on, SSE: keepalivecomments arrive every 15 s and the outcome is in the last chunk'sfinish_reason(stop,tool_calls,partial,no_output,request_budget), so a client never has to parse prose to know whether it got an answer.
Rate limits
Limits apply per API key, so one busy key never slows another. They depend on whether the account has paid:
| Limit | Free credit only | After any paid top-up |
|---|---|---|
| Requests per minute | 30 | 300 |
| Requests in flight at once | 4 | 32 |
| Spend per day (UTC) | $5 | your balance (a $500 daily safety limit applies) |
| File uploads per hour | 30 (per account) | |
You move to the higher column automatically within a minute of your first paid top-up. Need more than that, for example for a product with many users? Email support@tao1.ai and we will raise your keys.
When a limit is hit the API answers 429 with a Retry-After header (in seconds) and an error object whose type and code say which limit it was (rate_limit_exceeded with rate_limit_exceeded or concurrency_limit, or insufficient_quota with daily_spend_limit), plus the tier your key is on. Wait that long and retry; nothing is charged for a refused request. Every answer also carries x-ratelimit-limit-requests, x-ratelimit-remaining-requests and x-ratelimit-reset-requests so a client can pace itself. GET /v1/key/usage returns the calling key's own requests_today, cost_usd_today, in_flight and limits.
Errors
Errors are JSON with an error object. Status codes the gateway and the API answer:
| Status | When |
|---|---|
400 | Malformed body: empty or malformed messages; routing_generations not an integer in 1..8, or disagreeing with its alias (param names the field). |
401 | Missing or unknown key. |
404 file_not_found | A file_id that is not yours, was deleted, or expired after 30 days unused. |
413 file_too_large | A file over 50 MB or 2,000 pages. |
415 | A file type TAO-1 cannot read (for example a program, or an old .doc). |
429 rate_limited | More than 30 file uploads in an hour, or a per-key limit (see Rate limits). The Retry-After header says how long to wait. |
402 insufficient_credits | The balance cannot cover the $0.05 hold. Nothing was sent to TAO-1. |
404 | Unknown model. |
409 | A repeated Idempotency-Key. |
422 | The gateway refused the request shape. |
503 | The gateway or TAO-1 is temporarily unavailable; retry with backoff. |
504 no_output | The 780 s request total was reached with nothing served (non-streamed). |
Other endpoints
| Endpoint | Meaning |
|---|---|
GET /v1/models | The three model ids. |
POST /v1/feedback | A client signal on a request (thumbs, abandon, regenerate); stored with the regenerate policy. Body: signal (required, ^[a-z_]{2,32}$), request_id, session_id, value (number), note. |
GET /v1/key/usage | The calling key's own requests and spend today (UTC) and its limits. |
GET /v1/usage?session_id=… | Usage for a session (session_id required). |
GET /health | Liveness and the loaded artifacts; the deployed sha is code_commit. ?ledger=1 includes the ledger read. |
Use TAO-1 in Kilo Code and Cline
TAO-1 is an OpenAI-compatible chat model. Sign up at https://tao1.ai, create a key (it starts with jmk_), and point your tool at https://tao1.ai/v1. Model ids: tao-1 (balanced), tao-1-fast (cheaper). tao-1-best is coming soon. The API accepts images (see Images); we have not yet tested sending screenshots from inside Kilo or Cline, so if one fails there, turn the tool's image support off.
Kilo Code — CLI
Put your key in the environment (never in the file):
export TAO1_API_KEY=jmk_...
Add the provider to ~/.config/kilo/kilo.json (or ./kilo.json in a project):
{
"model": "tao1/tao-1",
"provider": {
"tao1": {
"name": "TAO-1",
"npm": "@ai-sdk/openai-compatible",
"env": ["TAO1_API_KEY"],
"options": { "baseURL": "https://tao1.ai/v1" },
"models": {
"tao-1": { "name": "TAO-1", "tool_call": true, "attachment": true, "limit": { "context": 131072, "output": 32768 } },
"tao-1-fast": { "name": "TAO-1 fast", "tool_call": true, "attachment": true, "limit": { "context": 131072, "output": 32768 } }
}
}
}
}
Then run kilo for the interactive agent, or headless:
kilo run --model tao1/tao-1 "Build a small CLI that ..."
Kilo Code — VS Code
Settings (gear) → Providers → Custom provider:
- Provider ID:
tao1· Display name:TAO-1 - Provider API: OpenAI Compatible
- Base URL:
https://tao1.ai/v1 - API key: your
jmk_key - Models:
tao-1,tao-1-fast(Kilo can also fetch them from the endpoint) - Submit, then pick
tao1/tao-1in the model picker.
Cline — CLI
cline auth --provider openai-compatible --apikey "$TAO1_API_KEY" --modelid tao-1 --baseurl https://tao1.ai/v1
cline -P openai-compatible -m tao-1 "Build a small CLI that ..."
(cline auth stores the key in Cline's own config; to avoid storing it, pass -k "$TAO1_API_KEY" on each run instead.)
Cline — VS Code
Settings → API Provider: OpenAI Compatible → Base URL https://tao1.ai/v1, API Key your jmk_ key, Model ID tao-1 (or tao-1-fast). Leave reasoning effort unset.
Known at launch
Streaming is on (since 2026-09-25). Kilo and Cline work with their default settings: responses stream, tool calls stream, and the charge for each turn is settled from the request's actual cost when the stream ends. Nothing to configure. Turns longer than about 3 minutes have not been tested through the edge yet.
