TAO-1/API reference ChatCode+

Talk to TAO-1 from code.

TAO-1 is an OpenAI-compatible chat model. Point any OpenAI client at https://tao1.ai/v1 with a key from your account, pick tao-1, and send messages. Responses use the standard OpenAI shape.

Base URLhttps://tao1.ai/v1
AuthAuthorization: Bearer jmk_…
Modelstao-1 · tao-1-fast
Free credit$5 on sign-up

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.

Streaming is on (since 2026-09-25). Set 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 idDialUse it whenPrice per M tokens (in / cached / out)
tao-1balanced (default)Most requests.$0.50 / $0.15 / $7.50
tao-1-fastcostOur fastest, lowest-cost dial: cheap, quick answers; high-volume calls.$0.25 / $0.075 / $4.00
tao-1-bestqualityThe 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

FieldTypeMeaning
modelstringOne of tao-1, tao-1-fast; default tao-1. The dial (see Models). Unknown → 404.
messagesrequiredarrayOpenAI 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.
streambooleanDefault 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_optionsobjectOpenAI shape; include_usage: true appends a final chunk with the billed token counts (streaming only). Non-streaming responses always carry usage.
toolsarrayOpenAI tool definitions; an empty array means no tools.
tool_choicestring | objectOpenAI tool_choice, passed through when tools are given.
routing_generationsinteger 1..8Default 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.
metadataobjectTAO-1-specific keys; see below.

metadata

KeyTypeMeaning
session_idstringThe 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.
objectivestringcost | balanced | quality. Overrides the model's dial for this request.
result_formatstringfiles | 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_generationsinteger 1..8Alias of the body field; must agree with it.
regenerate_ofstringThe request id the client rejected; the regenerate policy avoids its path.
privacybooleanMarks the request private: only enclave (TEE) providers may serve it; refused when none is available.
persona_namestring ≤ 40The assistant's name in the persona rules. Default TAO-1.
task_kindstringA caller's task hint passed to the build harness.
human_progresscomingbooleanNot 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

HeaderDirectionMeaning
AuthorizationrequiredrequestBearer jmk_…
Content-Typerequestapplication/json
Idempotency-KeyrequestOptional. 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:

FieldMeaning
choices[].finish_reasonstop | tool_calls | partial | no_output. partial = the answer was cut by a budget; no_output = nothing served (the message is in error).
james_buildWhen metadata.result_format is files: the build result — plan, files written, checks.
james_keepaliveOn 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": "![A weathered red vintage bicycle leaning against a textured cobalt blue wall](https://….public.blob.vercel-storage.com/jtao/b7cafcbb-40d6-45a2-b696-003e01297360.png)\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.

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.

EndpointMeaning
POST /v1/filesUpload a file (≤ 50 MB). Answers the file object.
GET /v1/filesYour files, newest first ({"object":"list","data":[…]}).
GET /v1/files/{id}One file object.
GET /v1/files/{id}/contentThe 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

SettingType and rangeDefault
routing_generationsinteger, 1–82 (see Routing generations)
leader_aia model id the provider serves, as provider/modelempty: today's measured leader for the task
follower_aia model id the provider serves, as provider/modelempty: today's measured follower
race_startseconds, 0–1000; 0 starts the leader at once; a value past the request's deadline is clamped to itempty: learned per model; 150 for Kimi K3
codevote_drawsinteger, 1–5: the number of parallel drafts on checkable coding problemsempty: the table value, 3 today
wall_window_sseconds, 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 turnsempty: the learned window for that model, capped by the deadline
intent_followupboolean: 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 askempty: off, and the reply is exactly what it is today
small_brief_tagswords 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 caseempty: 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

Coming: not yet available on the API. This section describes a planned feature. The deployed API does not read 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 504 with { "error": { "type": "no_output" } }; a streamed call ends with a final chunk whose finish_reason is no_output (nothing produced) or partial (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 200 whose 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 : keepalive comments arrive every 15 s and the outcome is in the last chunk's finish_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:

LimitFree credit onlyAfter any paid top-up
Requests per minute30300
Requests in flight at once432
Spend per day (UTC)$5your balance (a $500 daily safety limit applies)
File uploads per hour30 (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:

StatusWhen
400Malformed body: empty or malformed messages; routing_generations not an integer in 1..8, or disagreeing with its alias (param names the field).
401Missing or unknown key.
404 file_not_foundA file_id that is not yours, was deleted, or expired after 30 days unused.
413 file_too_largeA file over 50 MB or 2,000 pages.
415A file type TAO-1 cannot read (for example a program, or an old .doc).
429 rate_limitedMore 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_creditsThe balance cannot cover the $0.05 hold. Nothing was sent to TAO-1.
404Unknown model.
409A repeated Idempotency-Key.
422The gateway refused the request shape.
503The gateway or TAO-1 is temporarily unavailable; retry with backoff.
504 no_outputThe 780 s request total was reached with nothing served (non-streamed).

Other endpoints

EndpointMeaning
GET /v1/modelsThe three model ids.
POST /v1/feedbackA 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/usageThe 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 /healthLiveness 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-1 in 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.