API Changes
Changes to the Taiso API that a client may notice. Each entry says what a request now gets back, so you can check your error handling against it.
From API version 1.21.0
Check which version you are talking to with GET /api/v2/version:
curl https://api.taiso.ai/api/v2/versionThe newest changes come first, and each names the version it shipped in. The sections after them, from “404 if you cannot see it” on, shipped in 1.21.0.
The answer says why the model stopped (from API version 1.38.5)
POST /api/v2/llm/chatnow returnsfinish_reason, as the provider reported it:stop, orlengthwhenmax_tokensran out. A reasoning model can spend the whole budget before writing, so an emptymessage.contentwithlengthmeans: raisemax_tokens.POST /api/v2/llm/completionsandPOST /v1/chat/completionsreport the provider'sfinish_reasoninstead of alwaysstop, so an OpenAI client seeslengthwhen an answer was cut off. On/v1, an enhanced answer (one with ataisoblock) still reportsstop.
Intent extraction errors match the LLM endpoints (from API version 1.38.4)
POST /api/v2/tools/intents/extractno longer answers500when the model call fails. When no provider serves the requestedmodel, it answers503"No configured provider can serve this request right now". When the provider rejects the call, it answers asPOST /api/v2/llm/chatdoes:400"bad_request",404"model_not_found",429,502or503. The detail no longer repeats the provider's or our configuration's error text.
Removed self-test routes and search-engine fields (from API version 1.38.0)
GET /api/v2/tools/calc/testandGET /api/v2/tools/expression/testare gone (404). They ran built-in examples, not your input; usePOST /api/v2/tools/calcandPOST /api/v2/tools/expression.- Search has run on PostgreSQL alone for months, so the fields that described a separate search engine are gone:
searchfromGET /api/v2/health,elasticsearch_availablefrom search responses, andelasticsearch_indexedfrom file responses.GET /api/v2/healthnow answers"status": "ok"whenever the database is ok; it used to say"degraded"because of that engine. POST /api/v2/sops/generate-sop-schema,POST /api/v2/sops/generate-decision-blockandPOST /api/v2/agents/generate-agent-schemanow stop at the same 110-second deadline as the structured-block generators and answer504"deadline_exceeded", instead of retrying after the connection had already timed out.- A v2 SOP node whose
nextpoints at itself is reported as the cyclea -> a(withcycle_path["a", "a"]), nota -> a -> a. Other cycles are reported as before.
Limits on one request's work (from API version 1.37.0)
- A regular expression in
text-search,text-replace,text-extract, a form or DOCX extraction schema, or a JSON Schemapatternnow has 2 seconds on its text. Past that it is stopped:422from the text tools, a field error from the extractors, a failed validation in structured blocks. A pattern over 2,000 characters, or a replacement whose result would pass 20 million characters, answers400. In a JSON Schema this covers every pattern, including the matching insideadditionalPropertiesandunevaluatedProperties; a schema you pass that is not valid (say, a pattern that does not compile) answers400"invalid_schema"before any model call. The form and DOCX extractors take at most 200 fields, whose patterns share 5 seconds per request. - A JSON Schema in structured blocks (
json_schema,data_schema, and the schemagenerate-block-schemawrites) resolves$refand$dynamicRefonly within itself and the published JSON Schema meta-schemas. A URL is never fetched. A reference that does not resolve, or a$schemathat is not a string, answers400"invalid_schema"before any model call; a model-written schema with one is retried. A pattern nested too deeply to parse is also a400. taiso.policypattern lists on/v1/chat/completionsand/api/v2/llm(forbid_arguments,forbid_in_output,redact_in_output) must be lists of valid patterns, at most 2,000 characters each and 100 in all, or the request answers400. At run time each match has 0.25 seconds and a request's policy checks share 10 seconds of CPU; one that runs out of time refuses rather than allows.- A decision expression whose result would be too large (a list or string over a million elements or characters, counted through nested lists; a
%width or precision over 10,000 or given as*, also after a key as in%(n)d; areplace()past that size) is an expression error, and the decision takes its default branch.POST /api/v2/tools/expressionapplies the same%limits and answers400.sum()adds numbers only. - PDF, DOCX and HTML files are read in a separate process with a memory and time limit. A document that cannot be read within them answers
400(in a job: a failed step). A DOCX whose contents expand past 300 MB is refused unread. Pages are rendered at no more than 50 million pixels whatever DPI is asked for, and a file job'sdpiis capped at 600.convert-to-pdfanswers422past 2,000 pages, and itsoptions.marginmust be points or a length inin(orinch,inches),mm,cmorpt, from 0 to 40% of the page's shorter side (244.8 pt on Letter). Absent or""means one inch; anything else answers400, where an unreadable value used to mean one inch. text-diffanswers422past 500 lines per text or 50,000 characters forndiffandhtml(or when one takes more than 10 seconds: useunifiedfor large or very different texts), and past 50,000 lines or 5 million characters otherwise. Itschanges_summarycounts are unchanged.text-replacewithreturn_difffollows the unified limits.web-crawl-simple:timeoutis 1 to 30 seconds (422otherwise) and covers the whole fetch, redirects included. A page over 10 MB answers413, one that takes more than 10 seconds to convert422.textandmarkdownno longer include script contents. A successful page in an encoding other than gzip or deflate, or a corrupt compressed one, answers502; a redirect's body is not read, and an error page that cannot be decoded comes back empty with its status.- A structured-block request's model calls now stop at its deadline, raised from 100 to 110 seconds (
504 deadline_exceeded), instead of a call started before it running its whole timeout past it.
Webhooks, invitations, agents and keys (from API version 1.37.0)
- A webhook delivery must finish within 15 seconds in all, and at most 4 KB of your answer is read; only its status code counts. Answer quickly and do the work afterwards. A redirect (
3xx) is not a delivery: it is retried like any other failure. Staging's webhooks are now signed with staging's own key, not the public development key. - Opening an invitation link (
GET /api/v2/invitations/<token>) shows a confirmation page and spends nothing, so mail scanners and link previews cannot redeem it.POSTto the same URL redeems it and returns the key once, withCache-Control: no-store. - New or changed agent YAML may not use aliases (
*name) and may be at most 256,000 characters; either answers400. Agents saved with aliases before this version keep running and compiling. Changing an agent's definition pauses its schedules that belong to anyone else, with apaused_reason; they review the agent and resume them. A run such a schedule queued before the pause does not start. - An agent's compiled SOP changes only through the agent:
PUT,PATCH, rollback andDELETEon/api/v2/sops/<id>answer409with error type"compiled_for_agent", naming the agent. Change it withPUT /api/v2/agents/<id>, or delete the agent, which deletes its SOP. POST /api/v2/structured-blocks/generate-schemaand…/generate-block-schemastop at the request deadline with504"deadline_exceeded", as…/generateand…/transformdo.- The MCP
ask_questiontool needs theassist:writescope, asPOST /api/v2/assist/askdoes. PATCH /api/v2/users/mesets a new email only with an admin key; with any other credential it answers403(addresses change at taiso.ai, where they are verified).GET /api/v2/sops/categoriescounts public SOPs only.GET /api/v2/llm/modelslistsdeepseek/deepseek-r1-0528in place ofdeepseek/deepseek-r1, which OpenRouter no longer serves (requests for it answermodel_not_found). Switch any SOP or request that names the old id.- Issuing a key with
"unique_name": truerefuses a second active key of the same name with409,detail.code"name_taken"and the existing key'skey_id, before any key-limit refusal. Expired keys and keys made for agent runs do not hold a name. The Hub's service-key route sends it. - A conversation started by one user (
conversation_idon the generate-schema routes) can no longer be continued by another; their id starts a new conversation.
Keys issued by a service (from API version 1.36.0)
- A partner service that issues one key per customer on its own account now does it through the Hub, with its named service key:
POSTandGET /api/service/users/<id>/api-keys,DELETE …/api-keys/<key id>to revoke, andPUT …/api-keys/<key id>/rate-limit. Its key needskeys:readorkeys:writeand reaches only the accounts listed for it; any other account answers403. - A key can be issued with its own
rate_limit_per_minute(at most the account plan's) and its own monthly and daily spending caps. A second active key with the same name answers409with the first one'skey_id, so a retry cannot issue two. DELETE /api/business?id=…deletes a business the calling key created, while nobody belongs to it (409otherwise;404for another caller's). A named key listing businesses sees its own, plus those from before creators were recorded.
Streaming (from API version 1.35.0)
POST /v1/chat/completionsandPOST /api/v2/llm/completionswithstream: true(and no verification) now stream, as OpenAI'schat.completion.chunkevents ending indata: [DONE]. They used to acceptstream: trueand return one JSON body. Add"stream_options": {"include_usage": true}for a final usage chunk.- A streamed call that fails before its first token, on these routes or
POST /api/v2/llm/chat, now answers with the same status and JSON error as without streaming (404,429,503...). It used to be a200stream holding an error event. - The last event of a
/api/v2/llm/chatstream carriesfinish_reason, and the role is sent once rather than on every event. POST /api/v2/llm/simple-jsonwithstream: trueanswers422; it used to ignore it.- A stream ends after 10 minutes, with an error event of type
llm_timeout. - A bad sampling parameter (
"max_tokens": "lots") onPOST /v1/chat/completionsorPOST /api/v2/llm/completionsanswers400, streamed or not. It used to be503, which OpenAI's SDKs retry.
Webhooks (from API version 1.35.0)
- Every webhook is signed:
X-Taiso-Signature: t=<unix seconds>,v1=<hex>, HMAC-SHA256 of"<t>.<raw body>"with your secret, which is on your API Keys page (orGET /api/v2/webhooks/signing-secret; rotate it there or withPOST /api/v2/webhooks/signing-secret/rotate). Receivers that ignore the header are unaffected. - Every payload gains
delivery_id(also theX-Taiso-Webhook-Idheader, the same on every retry: dedupe on it),occurred_atandsequence. - A cancelled job now sends
job.cancelled. A SOP or agent run cancelled directly now sendssop_run.cancelledoragent_run.cancelled(it sent nothing), and a strict SOP run stopped for missing inputs sendssop_run.incomplete. A receiver that expects onlycompletedandfailedshould ignore other events. - A record that ends is announced exactly once, and a retry sends the same body as the first attempt.
- A
webhook_urlmust behttpson port 443. Another scheme or port is refused with400when the job or run is created, and a stored URL that breaks the rule is not delivered to.
Key scopes (from API version 1.35.0)
- Scopes given to a key when it is made now limit it:
<area>:readfor an area'sGETroutes,<area>:writefor the rest (see API reference). A key outside its scopes will get403; for now such calls are only logged, and refusing them follows after a notice here. - Keys made without scopes, which is every key made at taiso.ai, keep full access. An unknown scope is refused with
422when a key is made.
Code runs in an isolated runner (from API version 1.33.0)
- Programs sent to
POST /api/v2/code/stateless/python/execnow run on a separate, isolated runner, never inside the API. A program has no network, sees only its own working directory, and its environment holds only the runner's own variables (PATH,HOME,TMPDIRand a few Python settings). A program that relied on anything else will now fail. - Two new
503answers fromPOST /api/v2/code/stateless/python/exec:detail.errorisrunner_busy, withRetry-After: 5, when every runner slot is in use (retry then), orrunner_unavailablewhen no runner is reachable. Code never falls back to running in the API. - The same runner runs the calculations a knowledge-pack smart search makes, and the code checks Lodestar runs for a request with a
taisoblock (POST /api/v2/llm/chat,POST /v1/chat/completions). Without a runner, a smart search does not calculate, and such a code check fails with “code execution is not available on this deployment”.
Spending caps, keys and errors (from API version 1.33.0)
- A key over a spending cap is refused (
429) only by requests that can spend.GETrequests still work, so you can fetch a result you have already paid for or list your projects. Model calls check the caps whatever the route. - Creating a key past your limit (
POST /api/v2/users/me/api-keys) answers409with the same structureddetailas the admin route:code: "key_limit",limit,active,ceilingandmessage. It used to be a plain string. - A malformed
sop_refonPOST /api/v2/sops/{sop_id}/invoke(an empty id, a version that is not a positive number, extra parts) answers422rather than500. - Structured Blocks (
/generateand/transform): every retry of one request now fits in 100 seconds. Past that the answer is504withdetail.errordeadline_exceeded; try a smaller input.503now means only that no provider can serve the model you asked for. Any other failure is a500that names the kind of failure, and no error quotes a provider's reply any more. POST /api/v2/sops/generate-sop-schema: a provider that refuses our account for lack of credit (402) answers502 provider_auth_error, andlast_errorno longer carries the provider's reply.GET /api/v2/llm/modelslists only models a chat request can use: OpenAI's realtime, computer-use, image, speech and transcription models, and SambaNova's Whisper and E5 models, are gone. Two backends that serve the same model now list each other underalso_via, not only OpenRouter.- The public
GET /api/v2/healthnames a database failure only by its kind ("database": "error: <kind>"), no longer by its message.
Agents, audits and knowledge packs (from API version 1.33.0)
- The SLAF audit fails a block for contradicting its source only when it quotes the contradicting text from the source. A disputed verdict without such a quote no longer fails the block, and
POST /api/v2/structured-blocks/auditreports it as unclear. - Agents can choose how they are audited, as an SOP invocation can:
settings.execution_mode(strict, the default;relaxed, no audit; orcustom) andsettings.execution_config.audit_gate. Each audit is a model call and is billed as one. - Knowledge packs (
POST /api/v2/tools/knowledge_pack):max_nuggetsdefaults to32and goes up to100. It was 8 and 32 before API version 1.26.0, a change this page did not list then. Ask for8if you relied on the old default. corroborationsnow counts distinct sources: one section repeating a claim counts once.- When too many knowledge-pack searches are running at once, a search answers
503withdetail.error.typebusyandRetry-After: 5, rather than waiting without limit.
For administrators (from API version 1.33.0)
POST /api/v2/admin/users/{id}/invitationsneedsadmin:keys:write, since claiming an invitation makes a key; inviting an administrator also needsadmin:users:grant_admin.PATCH /api/v2/admin/users/{id}withstatus: "deleted"needsadmin:users:delete, asDELETEdoes.- An admin key stored with no scopes now has none; it used to have all of them. Ordinary keys without scopes are unchanged.
- A user can be created as a test account only with an address on a reserved domain (
example.comand the like,.test,.invalid,test.taiso.ai) or a tooling address; otherwise422. Giving a test account a person's email answers409. - Purging a user keeps their Taiso account id, so a later sign-in with the same identity is refused (
403) instead of creating a new account; creating a user with that id answers409. The purge also removes their IP address and email from the admin audit trail, which then reads[erased]. GET /api/v2/admin/usage/rollups?format=csvputs a quote before any text cell that starts with=,+,-,@, a tab or a carriage return, so a spreadsheet does not run it as a formula. Numbers are unchanged.
Plans and billing (from API version 1.32.0)
- A change to your plan reaches the API as soon as it is made: the plan's API key limit applies to
POST /api/v2/users/me/api-keysstraight away, rather than after the nightly copy. - Plans no longer list SOP executions a month or blocks per SOP: nothing enforced either. A plan lists only limits the API enforces. When Taiso switches plan spending caps on, an account's calls past its plan's monthly spending cap answer
429, like any spending cap, until the 1st (UTC); your plan's page on the Billing tab shows its cap once it applies. - For administrators:
GET /api/v2/admin/usage/rollupsaddsinvoiceable_cost_usd(billing cost less estimates, which are never invoiced) to every group and the totals,stale_days(days a cost correction has changed since they were rolled up) andclosed_periods;completeis now false while a day is stale.GET /api/v2/admin/usage/periods/{period}(scopeadmin:usage:read) says whether a month can be closed for invoicing, andPOST /api/v2/admin/usage/periods/{period}/close(scopeadmin:usage:write) closes it; afterwards its costs no longer change unless forced. A closed month's state shows any change since the close, overall (changed_since_close) and per account (changed_accounts); a second close of the same month answers withalready_closed: true. - For administrators: purging a user who is not a test account (
DELETE /api/v2/admin/users/{id}?purge=true) still anonymises them, but now keeps theirbusiness_id. It names an account, not the person, and their usage stays on that business's invoices.
Model call errors (from API version 1.32.0)
POST /api/v2/structured-blocks/generate-schema,/generateand/transformanswer502rather than500when the model gives no usable reply:detail.errorisllm_output_truncated(cut off at the model's output limit: ask for shorter output),llm_empty_replyorllm_invalid_json. A provider failure gets the same answer as on/api/v2/llm:502 provider_unavailable,429 rate_limitedand so on; a spending cap reached during the call is its429. Retry a502later; a500now always means our failure.POST /api/v2/structured-blocks/auditand/generate-block-schemaanswer a provider failure the same way./auditused to answer500, and/generate-block-schema422 GENERATION_FAILED.- Long transforms no longer fail early: the model may now write up to 8,192 tokens, where it had 2,048. A reply cut off is retried once at double that, if the request has time left. A very long input leaves itself room in the model's context, never under 2,048 tokens of output, so it is not refused for going over the context.
- On
/api/v2/llmand Structured Blocks, a provider call that timed out or lost its connection on every retry answers502 provider_unavailablerather than500. POST /api/v2/sops/generate-sop-schemaretries a provider only when that may help: a timeout, a lost connection,408,429or a5xx. A request the provider refuses (too long, say) answers422 GENERATION_FAILEDat once, withstage: "llm_error"and the provider's status inprovider_status. When the provider refuses our account, the answer is502 provider_auth_error, not your error.
Usage and costs (from API version 1.31.0)
total_requestson your API keys (GET /api/v2/users/me/api-keys) counts HTTP requests the key made, once each. It used to add one more for every model call a request made, so a key mostly used for model calls shows a lower number from now on. Model calls are in your usage instead.- Tokens a provider read from or wrote to its prompt cache, and reasoning tokens, are priced at their own rates: a cached prompt costs less than before, as the provider bills it.
cost_centsis rounded to the nearest cent on every route (some routes used to round down). - For administrators:
GET /api/v2/admin/logsreturns the last 7 days unless you passsinceanduntil; itstotalstops counting at 10,000 (total_capped); andip_addressis returned only withinclude_ip=true.GET /api/v2/admin/usage/rollupsreports usage per account, user, key or day.
How many API keys you can hold (from API version 1.30.0)
- Your plan sets how many active API keys you can hold: 10 on the free plan, and no limit on Enterprise. A safety limit of 1,000 active keys applies to every account. Creating another, through the API or this site, answers
409until you revoke one. - A plan's own limit reaches
POST /api/v2/users/me/api-keysonce the plan has been copied to SOP Engine; until then the API holds every account to the free plan's 10, while this site applies your plan. - Keys made for scheduled runs, and expired keys, do not count;
GET /api/v2/users/me/api-keysmarks them withagent_run_idandexpired.
A block that contradicts its source fails (from API version 1.28.2)
- In
strictmode, the default and the mode agents run in, the SLAF audit is now a gate. If it finds that a block's output contradicts its source (a field it classifies disputed), the block is generated and audited once more. If the second attempt is disputed too, the block fails, and the run fails with an error naming the fields:Block 'transform_data' failed its SLAF audit on all 2 attempts: document_title contradicts the source. Before, the run completed with the contradicting output. - A block that passes on its second attempt lists the first attempt's findings under
audit_retryin its output. The run's trace keeps a failed block's rejected output and its audit. - A field the audit could not find in the source (not_found) does not fail a block: a report or a judgment is not in its source by nature. An audit that could not run never fails a block.
- In
custommode,execution_config.audit_gatechooses the gate:"off","disputed"(the default) or"failed"(the audit's overall pass mark, which also counts fields it could not find). Any other value is refused at invoke with422.execution_configused to be accepted and ignored. - If a run now fails its audit, give the failing step the data its prompt talks about. A step told to fix or report on data it never receives invents it.
Spending caps on your account (from API version 1.28.0)
- Taiso can now set spending caps on your account as a whole, across all your keys and your sessions on this site: tokens a day, spend a day and spend a month. No account has one unless Taiso sets it.
- A request or model call over an account cap gets
429. The message says it is a limit Taiso set on your account, not a problem with your key, which cap was reached and when it resets (00:00 UTC, or the 1st of the month). You cannot see your account's caps yet; ask support. A call must pass both its key's limits and its account's. - While an account is over a cap, its scheduled agent runs are skipped when they fire. The schedule stays on and runs again once the cap resets.
Knowledge packs: smart mode (from API version 1.27.0)
- The model calls a
"mode": "smart"search makes (POST /api/v2/tools/knowledge_pack, up to 8 at the default) are now in your usage and count toward your spending limits. They were not recorded before, so smart searches now show a cost. - If a limit is reached during a smart search, the search stops and returns what the plain search found, with
verification.stop_reasonset to"reader_budget". That result is incomplete; if correctness matters more than getting an answer, treat it as a refusal. - New
distinct: truereturns one result per claim instead of one per citation, keeping each claim's best-ranked citation. Every result now carriescorroborationsandcorroborating_sources(1and[]unless duplicates were collapsed into it). Withdistinct,max_nuggetscounts claims, so a large value can come back short.
SOP block prompts are instructions (from API version 1.26.2)
- A SOP block's prompt is now carried out as an instruction. Before, it was treated as text to extract data from, so a block asked to write a report could return its own instructions as the report. A prompt phrased as extraction (“Extract the names from…”) works as before. Agents need no recompiling.
POST /api/v2/structured-blocks/generatetakesmode:"extract"(the default, as before:datais source text) or"instruct"(datais instructions to carry out, and the result comes back in your schema). Any other value is422.- A block's audit can take up to 120 seconds (it was 60), so fewer audits are recorded as unavailable.
404 if you cannot see it, 403 if you cannot change it
Every project, file, chat, job, SOP and agent now follows one rule:
- It does not exist, or you have no access to it:
404, “… not found or you do not have access”. Before, some endpoints answered403here, which confirmed the id was real. - You can see it, but your role is too low for what you asked:
403, naming the role required. Ask the owner to share it with you as an editor.
If your client treated 403 as “no access”, treat 404 the same way.
Runs belong to their project
- A SOP run, agent run or agent schedule is visible to anyone with a role on its project, as jobs already were. Before, agent runs were visible to anyone who could see the agent (for a public agent, everyone), and SOP runs only to whoever started them. Teammates now see each other's runs; a stranger gets
404. - Cancelling a run (
DELETE /api/v2/sops/runs/{run_id},DELETE /api/v2/agents/runs/{run_id}) needs editor on its project, like cancelling a job. Cancelling an agent run used to answer500; cancelling a SOP run now also cancels its job. - A job of type
agent_executioncan only start one of your own, not-yet-started agent runs in the same project; anything else is404(or409if the run already started). project_idonPOST /api/v2/sops/{sop_id}/validateand onPOST /api/v2/llm/chatneeds a role on that project; otherwise404.
Writing into a project needs the editor role
A viewer can read a project; changing anything in it needs editor. These calls accepted a viewer before and now answer 403:
- Invoking a SOP or an agent into a project (
POST /api/v2/sops/{sop_id}/invoke,POST /api/v2/agents/{agent_id}/invoke). - Creating or updating an agent schedule that writes into a project (
POST /api/v2/agents/{agent_id}/schedules). - Converting a project file to PDF.
- Deleting a job, even one you created.
- Processing a file for search (
POST /api/v2/files/{file_id}/process). - Recompiling an agent, which now needs editor on the agent.
Reading a SOP's or agent's usage statistics now needs at least viewer access to it.
Schedules pause when their creator loses access
Each time a schedule fires, it checks that its creator can still edit the target project. If not, the schedule is switched off instead of running, and its response says why in paused_reason, with the time in paused_at. Turning it back on clears both.
A schedule's runs belong to whoever created it or last changed it; changing one needs editor on its project and makes you that user. A schedule also pauses when its user can no longer run the agent.
Schedule responses now return the inputs you set in input_config; before, it was always empty. Jobs started by a schedule have ids like every other job (job-1a2b3c…).
Scheduled runs and shared agents now run
- Every scheduled agent run used to fail with “This run has no API credential”. Each firing now gets a key of its own, for the schedule's user: it can spend at most $1.00, expires after two hours and is revoked when the run ends. It appears in your key list as
Scheduled run arun-…. - A run of an agent shared with you (as
executororeditor) used to fail with “SOP not found” or “permission denied”. It now runs, as you.
Authentication
- A request without a key gets a
401that says how to get one, with aWWW-Authenticate: Bearerheader. The message still starts “Missing Authorization header.” - A token that is neither an API key nor a session token is a
401. It could be a503before. beareris accepted as well asBearer.- The OpenAPI document declares a Bearer security scheme and the server URL, so the interactive docs have an Authorize button. Some operations no longer list
Authorizationas a header parameter. If you generate a client from the spec, regenerate it: the key now comes from the client's auth setting.
Errors
- An unexpected server error is still a
500with a request id, but the connection stays open. Before, the next request on the same connection could fail with “Server disconnected”. - When file storage is unavailable, file calls answer
503instead of500. Retry later. - Fetching the result of a job you cannot access is a
404, not a500. Deleting an agent that does not exist is a404, not a403. - Sharing an agent with a user who does not exist is a
400, like sharing a SOP, not a500.
A pinned SOP version is the one that runs
Invoking a SOP with sop_ref: "sop:<sop_id>:<version> (or version) was accepted but ran the SOP's current version. It now runs the version you pinned, and GET /api/v2/sops/runs/{run_id} reports it as sop_version. If you pinned versions before, check runs that depended on an older definition.
Schedules, jobs and webhooks
- A schedule's
timezoneis now applied. It was stored but ignored, so every schedule ran on UTC. A schedule with"0 9 * * *"andAmerica/New_Yorknow fires at 9 a.m. New York time. An unknown timezone is refused. - An
http_calljob'sendpointmust be a path of this API under/api/v2/; anything else is400. - A
webhook_urlmust be a public address; local and private addresses are refused. - Agent steps of
type: sopandtype: agentare refused when an agent is created: they never actually ran a nested SOP or agent. Chain runs yourself instead. Idempotency-Keyvalues are your own: one another user has used no longer fails your request.
API keys and uploads
- A key with spending or token limits, an expiry, or one made for a scheduled run cannot create keys through
POST /api/v2/users/me/api-keys(403). A name must be 1 to 100 characters. Scopes may be narrowed but not set to*or an admin scope (422); they are recorded and not yet enforced. - Knowledge-pack downloads are counted per account (10 a month across all your keys), not per key.
- Spending limits are checked before every model call, not only when a request starts: a run stops at the call that would go past a limit (
429).max_tokenson a chat request is at most 65,536. A streamed call cut short is still recorded, with its usage estimated. - Your email address is changed at taiso.ai, where it is verified;
PATCH /api/v2/users/merefuses it with403. - Deleting an agent while one of its runs is in progress answers
409. - A schedule fires once at each local time, including when daylight saving ends (it fired twice in the repeated hour).
- A request body over 100 MB, uploads included, is refused with
413, as is a file larger than your remaining storage. - A
500no longer includes the underlying error text. Quote itsrequest_idwhen you contact support.
MCP server
/mcp/rpcnow speaks MCP:initialize,pingand notifications are answered, so MCP clients such as Claude Code can connect. Before, every client failed atinitializewith HTTP 400.- Errors are JSON-RPC errors (
-32601unknown method,-32602unknown tool or bad arguments,-32002unknown resource) instead of HTTP 400 and 404. Tool arguments are checked against each tool's published schema. generate_codetakestaskandlanguage(pythonorbash), and its snippets run as written.get_agent_templateoffersresearch,document_analysis,scheduled_report,multi_agentandcontent_pipeline; each template is valid agent YAML, which none of the old ones was./mcp/sseis retired and answers410. It never handled MCP messages.generate_code'staskis one of its listed tasks exactly. Text arguments have length limits (maxLengthin each schema). A browser request from an origin the API does not allow is403, and an unsupportedMCP-Protocol-Versionheader is400.
Documentation: the id format
Ids are a short prefix, a dash, then hex: prj-1a2b3c4d5e6f, sop-7b19ee0d. Some examples showed prj_abc123, which the API never issued. If your client validates ids against that pattern, accept the dash. A pinned SOP reference is sop:<sop_id>:<version>.
Assist
The step-by-step answers from POST /api/v2/assist/ask now name only endpoints, job types and request bodies the API accepts. Some answers used to suggest calls that failed; if you copied one, compare it with the answer you get now.
New to the API? Start with Getting Started.
Need help?
This page is a starting point. For anything it does not cover, reach out.