What you need
- A deployed release in an environment; see Deploying, environments and approvals.
- A runtime key for that environment, from API keys → Runtime keys. Keys for a production
environment start
tf_live_, the otherstf_test_. The key decides which environment answers. Each key belongs to an Application, so every call in the Decision log says which of your systems made it. A runtime key can only call Decisions; it can't edit, publish or deploy. Making runtime keys needs a plan that includes production. - For a BYOK or Relay environment, your own key for TypeSafe, the AI model provider that answers every Decision.
BYOK, Managed and Relay
Each environment says how callers reach TypeSafe (Environments → How callers reach TypeSafe):
- BYOK (bring your own key) is the default for new production environments. Each call carries
your TypeSafe key, which is used for that call only and never stored. A call without one is
refused with
400 TYPESAFE_KEY_REQUIRED. A BYOK environment also accepts relayed calls. - Relay is for a TypeSafe key that must never leave your own systems, because a policy, a
contract or a regulator says so. TypeFuse prepares the exact request, your app sends it to
TypeSafe with its own key and hands the response back, and TypeFuse checks it, records the run and
answers as usual. Calls are a little slower, shadow releases don't run, replays of production leave
these calls out, and the record marks each response as reported by your app. A Relay environment
accepts only relayed calls; a plain call is refused with
409 RELAY_REQUIRED. - Managed means TypeFuse's own TypeSafe key answers, and its usage is billed. Environments that
aren't production, such as Test, start as Managed, and so does production on Enterprise, where
Managed is part of the contract. Relayed calls are refused with
409 ROUTE_NOT_ENABLED.
Choose BYOK unless your key must never leave your systems: it is the simpler integration and the faster call. In every case the request is validated, the release chosen and the answer recorded on TypeFuse's servers, and the answer has the same shape.
HTTP
curl https://run.typefuse.dev/v1/acme/refunds \
-H "Authorization: Bearer $TYPEFUSE_KEY" \
-H "X-TypeSafe-Key: $TYPESAFE_KEY" \
-H "Content-Type: application/json" \
-d '{"input": {"request": "Arrived damaged", "days_since_delivery": 12}, "accepts": "^1.0.0"}'
Leave out X-TypeSafe-Key for a Managed environment. The body takes:
input: the state your Definition'sstate_schemadescribesaccepts: the releases you accept, such as^1.0.0(see Versions and releases), orversion, an exact revision, but not both. TypeFuse never falls back to another major line on its own: a caller that accepts^1gets a 1.x release or409 INCOMPATIBLE_VERSION, never 2.xsubject(optional): a stable id for the thing being decided, so a canary gives it the same release every timereference(optional):{ "type": "order", "id": "A-1042" }, your own record this call is aboutcorrelationId(optional): an id that ties the call to the rest of your request; theX-Correlation-Idheader works too
The answer:
{
"runId": "run_…",
"decision": "refunds",
"identifier": "acme/refunds",
"environment": "production",
"version": 4,
"semanticVersion": "1.2.0",
"role": "primary",
"answers": { "eligible": { "type": "noul", "noul": 0.94 } },
"latencyMs": 412
}
GET https://run.typefuse.dev/v1/acme/refunds with the same key describes what is deployed: the releases, the
state schema and the questions.
Errors come back as { "code", "message" }. The ones to handle:
| Status | Code | Means |
|---|---|---|
| 400 | INVALID_INPUT | The input doesn't fit the state schema |
| 400 | TYPESAFE_KEY_REQUIRED / TYPESAFE_KEY_NOT_USED | A BYOK call without a key, or a Managed call with one |
| 401 | UNAUTHORIZED | The runtime key is missing or wrong |
| 402 | PLAN_LIMIT | Your plan doesn't serve this call; see Plans and usage |
| 404 | NOT_DEPLOYED / VERSION_NOT_DEPLOYED | Nothing, or not that revision, is deployed in the key's environment |
| 409 | INCOMPATIBLE_VERSION | No deployed release is in the range you accept |
| 429 | RATE_LIMITED | Your organisation has made its plan's calls for this minute, across all its keys; wait for Retry-After |
| 502 | PROVIDER_ERROR | TypeSafe couldn't answer; the call is still recorded |
| 503 | NOT_RECORDED | With "Record before answering" on: the call couldn't be recorded, so no answer was sent; try again |
Every call is recorded. Your answer is sent as soon as the model replies, and its record follows
within seconds; if storing it fails, TypeFuse keeps retrying from a second copy. For work that must
never give an answer without its record, owners and admins can turn on Record before answering
(Environments → Recording calls): each call then waits for its record, about a tenth of a second
more, and you get 503 NOT_RECORDED instead of an answer if it can't be kept.
SDK
@typefuse/sdk is a small client for the same HTTP API. It is not on npm yet; it will be published
with the move to the typefuse.ai domain.
import { createRuntime } from "@typefuse/sdk";
const typefuse = createRuntime({
apiKey: env.TYPEFUSE_KEY,
typesafeKey: env.TYPESAFE_KEY, // leave out for a Managed environment
});
const { answers } = await typefuse.decide(
"acme/refunds",
{ request: "Arrived damaged", days_since_delivery: 12 },
{ accepts: "^1.0.0", reference: { type: "order", id: "A-1042" } },
);
accepts can also be a list, such as ["^2.0.0", "^1.0.0"], tried in order. That is your code
saying it accepts both contracts, so use it only when your code handles both answer shapes: a new
major can change the input, the questions or the answers. The result's version and
semanticVersion say which release answered; check them before reading the answers.
The SDK retries briefly when the runtime is busy or unavailable, and turns error codes into typed
errors such as InvalidInputError, IncompatibleVersionError and PlanLimitError. noul(),
choice() and score() read an answer and check its type, and perItem() pairs
per-item answers with their items.
For a Relay environment, add route: "relay". The SDK then sends your TypeSafe key only to
TypeSafe, never to TypeFuse:
const typefuse = createRuntime({
apiKey: env.TYPEFUSE_KEY,
typesafeKey: env.TYPESAFE_KEY,
route: "relay",
});
const { answers } = await typefuse.decide("acme/refunds", input, { accepts: "^1.0.0" });
await typefuse.flush(); // before your process exits
flush() waits for any completion still being retried in the background.
Relay without the SDK
A relayed call is three requests. Never send X-TypeSafe-Key to TypeFuse on Relay.
POST https://run.typefuse.dev/v1/acme/refunds/preparewith your runtime key and the ordinary call body. It answers with atoken, the release that will answer, andcall: theurl,method,headersandbodyto send to TypeSafe. When there is nothing to ask (every question is per item and every list is empty),callisnulland the answers are already there.- Send
callto TypeSafe withAuthorization: Bearer <your TypeSafe key>, passingcall.bodyexactly as given, without parsing or re-serialising it. POST https://run.typefuse.dev/v1/acme/refunds/completewith{ "token": …, "response": { "status", "body", "requestId", "sentAt", "receivedAt" } }, wherebodyis TypeSafe's response text as received andrequestIditsx-typesafe-request-idheader. If TypeSafe couldn't be reached, send{ "token": …, "response": { "error": "network", "message": … } }. It answers in the ordinary shape.
The token can be used once and lasts 10 minutes; repeating the same completion is safe and gets the
same answer. A call counts once, when it is prepared, and a prepared call that is never completed
shows in the Decision log as abandoned. A response that doesn't fit the request is refused with
502 PROVIDER_ERROR and recorded as an error.
CLI
The typefuse command calls a Decision the same way. Like the SDK, it isn't on npm yet.
typefuse call acme/refunds --key $TYPEFUSE_KEY --typesafe-key $TYPESAFE_KEY \
--input '{"request": "Arrived damaged", "days_since_delivery": 12}' --accepts ^1.0.0
--reference order:A-1042, --correlation and --subject match the HTTP fields.
MCP
Agents connected over MCP build, test, publish and deploy Decisions (see Connecting an agent). They don't call deployed Decisions through MCP: your software calls the runtime, as above. The full list of control-plane endpoints is in the API reference.