Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Make your first routed request

Send one request through your assigned gateway with a runtime key, and read back the model the gateway chose.

TutorialLast updated

On this page

In this tutorial we’ll send one chat completion through an assigned TokenTriage gateway and print the answer, the request ID, and the provider model the gateway picked for it.

Your application carries one credential: a TokenTriage runtime key, the ttv_-prefixed secret minted for a service identity. It carries no provider key. Your OpenAI or Anthropic credential is stored in TokenTriage as a saved connection and attached on the way out to the provider, by a process your application does not talk to. That is what saved mode means, and it is the only credential mode the hosted product admits today.

A runtime key opens one door: inference through the gateways its allowlist names. It cannot read the dashboard and it cannot call the management API. Those take a portal session or a management key (ttm_…), which is a different credential with a different lifetime.

What you need

Thing Where it comes from
runtime key (ttv_…) tokentriage cloud credential create, printed once
assigned gateway base URL the application’s gateway page
gateway ID (gw_…) the same page; the credential’s allowlist names it
environment ID (env_…) the workspace’s environments; the credential binds one
the local SDK build install the local build

You do not need a provider API key, a config file, a database, or dashboard access. The saved provider connection and the credential are set up once by whoever holds connections.manage and keys.manage; adding a saved provider connection is that side of the work.

About two minutes once you hold those four values. Minting the credential and saving the provider connection are not counted; they happen before this page.

1. Know the shape you are about to send

Four things carry a saved-mode request, and one of them is the body:

POST <assigned-gateway-base>/v1/chat/completions
Authorization: Bearer <TokenTriage runtime key>
X-Tokentriage-Gateway: gw_…
X-Tokentriage-Environment: env_…

{"model": "gateway", …}

That block describes the SDK’s request contract. The executable examples below are checked over real loopback HTTP with synthetic responses; live acceptance separately exercises the packaged applications against saved connections. These are different layers of evidence.

Read it line by line, because every line inverts something you may have done before. Authorization is ours in this mode: it holds the runtime key, because there is no provider key to put there. X-Tokentriage-Gateway is required even when the credential allows one gateway, so that every example and every runbook reads the same. X-Tokentriage-Environment may be omitted only when the credential binds one environment, and a value you do supply has to be that one.

"model": "gateway" is a sentinel, not a model name. The gateway’s applied release decides which provider model serves the request and substitutes it before dispatch, so the literal string gateway reaches no provider. Naming a provider model yourself is refused rather than quietly ignored.

2. Send it from Python

The packaged client stamps all three headers for you. connection.openai() takes the two selectors and no provider-key argument at all.

import os
import sys

from openai import APIError
from tokentriage.client import TokenTriage, TokenTriageError, error_details

NAMES = ("TOKENTRIAGE_GATEWAY_BASE", "TOKENTRIAGE_RUNTIME_KEY",
         "TOKENTRIAGE_GATEWAY_ID", "TOKENTRIAGE_ENVIRONMENT_ID")
missing = [name for name in NAMES if not os.environ.get(name)]
if missing:
    sys.exit(f"Set {', '.join(missing)} before running this example.")
base, runtime_key, gateway_id, environment_id = (os.environ[name] for name in NAMES)

try:
    with TokenTriage(base_url=base, api_key=runtime_key) as connection:
        client = connection.openai(gateway_id=gateway_id, environment_id=environment_id)
        raw = client.chat.completions.with_raw_response.create(
            model="gateway",
            messages=[{"role": "user", "content": "Say hello in one sentence."}],
            max_tokens=128,
        )
        answer = raw.parse().choices[0].message.content
except (TokenTriageError, APIError) as error:
    # A configuration error is raised locally and never reaches the gateway.
    details = error_details(error)
    sys.exit(f"Request failed: category={details.category} code={details.code} "
             f"status={details.status} request_id={details.request_id}")
except Exception as error:
    sys.exit(f"Request failed: {error!r}")

request_id = raw.headers.get("x-tt-request-id")
model = raw.headers.get("x-tt-model")
print(answer)
print("Request ID:", request_id)
print("Model:", model)
if model in (None, "gateway"):
    print("Warning: x-tt-model is missing or still 'gateway'; check the gateway's applied release.",
          file=sys.stderr)
if request_id is None:
    sys.exit("No x-tt-request-id was returned; the response did not come from a TokenTriage gateway.")

# A successful run prints, for example:
#   Hello! How can I help you today?
#   Request ID: req_...
#   Model: <provider model chosen by the applied release>

How to tell it worked. The answer prints first. Then x-tt-request-id, which is the ID to quote when you ask for help, and x-tt-model, which holds the provider model the release selected. An x-tt-model that names a provider model rather than the string gateway is the evidence that routing happened. x-tt-release, x-tt-gateway and x-tt-environment are on the same response if you want to record which configuration served the call.

This exact snippet runs in the SDK’s local HTTP checks. The packaged B1 call site in examples/acme-hr-portal has also completed ordinary and streamed requests against the private deployment, with the saved HR connection and priced customer receipts. That live application run is separate from execution of this shorter snippet.

3. Or from TypeScript

Run this in your backend, where the runtime key stays private.

import OpenAI from "openai";
import { TokenTriage, TokenTriageError, errorDetails, gatewayModel } from "tokentriage/client";

const names = ["TOKENTRIAGE_GATEWAY_BASE", "TOKENTRIAGE_RUNTIME_KEY",
  "TOKENTRIAGE_GATEWAY_ID", "TOKENTRIAGE_ENVIRONMENT_ID"];
const missing = names.filter((name) => !process.env[name]);
if (missing.length > 0) {
  console.error(`Set ${missing.join(", ")} before running this example.`);
  process.exit(1);
}
const [baseUrl, apiKey, gatewayId, environmentId] = names.map((name) => process.env[name] ?? "");

let connection: TokenTriage | undefined;
try {
  connection = new TokenTriage({ baseUrl, apiKey });
  const client = await connection.openai({ gatewayId, environmentId });
  const { data, response } = await client.chat.completions.create({
    model: gatewayModel,
    messages: [{ role: "user", content: "Say hello in one sentence." }],
    max_tokens: 128,
  }).withResponse();
  const requestId = response.headers.get("x-tt-request-id");
  const model = response.headers.get("x-tt-model");
  console.log(data.choices[0]?.message.content);
  console.log("Request ID:", requestId);
  console.log("Model:", model);
  if (model === null || model === "gateway") {
    console.warn("Warning: x-tt-model is missing or still 'gateway'; check the gateway's applied release.");
  }
  if (requestId === null) {
    console.error("No x-tt-request-id was returned; the response did not come from a TokenTriage gateway.");
    process.exitCode = 1;
  }
} catch (error) {
  if (error instanceof TokenTriageError || error instanceof OpenAI.APIError) {
    // A configuration error is raised locally and never reaches the gateway.
    const details = errorDetails(error);
    console.error(`Request failed: category=${details.category} code=${details.code} `
      + `status=${details.status} request_id=${details.requestId}`);
  } else {
    console.error("Request failed:", error);
  }
  process.exitCode = 1;
} finally {
  connection?.close();
}

// A successful run prints, for example:
//   Hello! How can I help you today?
//   Request ID: req_...
//   Model: <provider model chosen by the applied release>

gatewayModel is the exported sentinel, so you import the string rather than retyping it. The same three identities come back on response.headers. This exact snippet runs in the SDK’s local HTTP checks. The stock OpenAI integration in sdk/examples/existing_openai.ts has separately served live A1 requests, including after process replacement with the same retained runtime key.

If your client insists on an apiKey

The official OpenAI constructor wants a nonempty apiKey, and in saved mode that value is ours. The SDK supplies the runtime key itself, so pass no apiKey: one you pass is read as a provider key aimed at the header the runtime key owns, and the SDK declines to build the request with provider_key_not_allowed before anything reaches the network.

4. Read the refusal you got instead

Saved mode decides one credential arrangement and one body shape, so a mistake comes back as a named code rather than as a provider error. These are the seven you are most likely to meet, each with its fix.

Code Status The likely cause What to change
provider_credential_not_allowed 401 a provider key travelled with the request, on Authorization or on a carrier such as X-Api-Key remove it; the stored connection supplies the provider credential
ambiguous_credential 400 two credentials named at once: Authorization sent twice, or a runtime key beside X-Tokentriage-Key send the runtime key once, on Authorization, and drop the legacy account header
gateway_required 400 no gateway selector on the request set X-Tokentriage-Gateway, or pass gatewayId / gateway_id; there is no default gateway
gateway_not_found 404 the gateway is unknown, archived, or outside this credential’s allowlist check the ID against the application’s gateway page, then the credential’s allowlist
environment_not_allowed 403 the gateway is allowed but the environment is not the credential’s own send the credential’s environment, or mint a credential for the environment you meant
model_override_unsupported 422 the body named a provider model instead of the sentinel send "model": "gateway" and let the release choose
no_eligible_target 422 no target in the gateway’s deployed route can serve this endpoint or these request parameters, for example an embeddings model on a chat route publish and deploy a release whose route includes a target model that supports this endpoint, then retry

Two of those answers are less specific than they look, for a reason. gateway_not_found is the one answer for unknown, archived and another organization’s gateways alike, so no request can be used to discover which gateway IDs exist. ambiguous_credential is a 400 rather than a 401 because nothing about either credential was judged: the request itself is malformed.

A 503 naming release_unavailable is a different class again. It says the gateway holds no live lease on an applied release, so check deployment status before another attempt. It is not a credential problem. If a transport failure leaves a paid outcome unknown, retain its request ID and inspect the receipt before resending; do not silently replay it.

Structured error metadata reads the category, status and request ID off the exception the provider client raises.

What this page does not cover

Streaming, async clients and keeping your own transport are on provider clients and streaming. Request-supplied mode, where a caller sends a provider key on X-Tokentriage-Provider-Key, is gateway configuration rather than a client choice, and the hosted product does not admit it today. Keep the request ID to retrieve the scoped customer receipt; traffic and spend reads explain its usage, release and cost states.