Call an external API with an API key

Prompt:

I stored my weather service's API key on the Turn Zero dashboard as WEATHER_API_KEY. Use it to fetch the forecast for each listing.

Also works:

  • "Call the Anthropic API from the backend with the key I saved on the dashboard as ANTHROPIC_KEY."
  • "Post every new order to our fulfilment partner's webhook. Its key is on the dashboard as PARTNER_KEY."
  • "The payments key leaked. I stored the new one on the dashboard under the same name."

What your tool does

  • Chooses the route from your request, as step 1 describes. It uses the gateway for a call with a stored key or under the AI allowance, and the tunnel for a host that needs no key.
  • For an application programming interface (API) that takes a key, asks for the name you stored the key under on the dashboard, where the prompt does not say. It confirms the name with list_secrets. Where the key is not stored yet, it follows the store-key skill. It asks for the key's file path and the name to store it under. It then calls store_secret with that path as value_file and runs the command line the call returns, so the value never enters the conversation. Store a secret describes the line.
  • Stores the key for one application and environment when production and development use different keys, or at account scope when they share one. Local runs also use the development key.
  • Calls list_upstreams to see whether the name already exists and what it is bound to. Then it calls declare_upstream with name, base_url, credential_name, auth_header, and application, adding auth_format and token_shape where they apply.
  • Where the manifest keeps the application's declared upstreams, adds the declaration to its upstreams member and resubmits instead (Declare in the manifest).
  • Takes the Egress package from the library (Use the library). It notes each upstream and its credential name in the project beside the package, and writes the backend call to /egress/v0/<upstream>/<path> under the application's platform credential.
  • Where the backend calls a provider through the provider's own software development kit (SDK), leaves that code unchanged and declares the upstream with settings, naming the settings the SDK reads (An unchanged SDK).
  • For text generation under the allowance, takes the AI Allowance package and calls its generateText function. It calls under the application's platform credential, or under a token for the application with the environment header. On allowance_exhausted or allowance_unavailable, it falls back to a declared upstream on your stored key.
  • For a host that needs no key, adds the hostname to the manifest's egress list and resubmits with submit_manifest, following the manifest-author skill. It writes the call with a client the tunnel covers (Egress firewall and request limits).
  • Offers, before any deploy, a run on your machine that tests the stored key through the gateway, as step 4 describes.
  • Deploys the code change with deploy and checks it with read_status until it ends. A manifest edit takes effect with that deploy.
  • After the first calls, calls read_logs with source: "egress" and filter: "undeclared" for tunnel destinations the manifest does not list. It then adds each host to the list or removes the call.
  • On a leaked key, replaces the key with its issuer and calls rotate_secret under the same name, application, and environment, and runs the line it returns.
  • Asks for no browser approval: store_secret, rotate_secret, declare_upstream, submit_manifest, and deploy are reversible-tier actions.

What you need

  • The API key, stored on the Turn Zero dashboard. Sign in at turnzero.ai and open Home. Under Secrets, in Store a new secret, enter a name such as WEATHER_API_KEY, paste the key as the value, choose account as the scope, and choose Store. The account scope lets both development and production use the key.
  • The name you gave the key. The value is write-only: nobody can read it back, your AI included.
  • Where the service's documentation says the key goes, if you know it: the header name, and whether it needs a prefix such as Bearer. Your AI can usually find this itself.
  • For a host that needs no key: the exact hostname in lowercase, or a wildcard domain such as *.example.com.

Before your AI starts

This section is for your AI tool: what it checks and gathers before it begins. You don't need to do these steps yourself.

  • A connected, signed-in tool (Connect your tool) and an application with a submitted manifest (Deploy an application).
  • The application's identifier, from list_applications, where the upstream or the secret belongs to one application.

Steps

1. Choose the outbound path

Choose the outbound path, the gateway or the tunnel, by whether the call uses a stored key:

  • A keyed API is stored, declared, and called through the gateway (steps 2 to 6).
  • A call under the plan's included AI allowance also goes through the gateway, on an API key the platform keeps, so it needs no stored key (step 5).
  • A call to the application's own issue-tracking space also goes through the gateway, on the platform's issue-tracking upstream (step 5).
  • A host that needs no key is listed in the manifest and reached through the tunnel (step 7).

The two routes keep different records (step 8). The tunnel writes connection records, which you read with read_logs. The gateway writes a usage row per call instead, which read_logs does not show. read_usage counts the bytes each route sends under data transfer.

2. Store the API key

A key stored on the dashboard is already in custody: your tool confirms its name with list_secrets and goes on to step 3. Otherwise it stores the key under a name with store_secret. The call takes no value: it returns a command line that sends the value from your machine. Store a secret shows how the line keeps the value out of chat. Store the key before you call declare_upstream, which refuses a name that is not stored. A declaration in the manifest is accepted first, and the gateway refuses its calls until the key is stored.

One rule chooses between the gateway and a binding, and Store a secret states it whole, with its example.

An upstream's key never becomes an environment variable of the deployed application. Code that reads it from process.env finds nothing there. The code calls the gateway instead, and the gateway applies the key. A value the manifest's settings bind does become a setting, but a key an upstream names can never be bound (Store a secret).

The application's own process never has the key, unless the upstream sends it back. From the upstream's response, the gateway removes the auth_header header, Set-Cookie, the hop-by-hop headers, Content-Encoding, and Content-Length. The body and the other headers pass through unchanged. An upstream that echoes the key in its response body returns it to your process, so declare only an upstream you trust with the key.

One declaration covers production, the development environment where the application has one, and local runs, which use the development key on every application. Store the key at development and at production when the two keys differ, or once at account scope when they are the same. store_secret and rotate_secret default to production, so a development key needs environment: "development".

The gateway uses the key stored for the calling environment, or else the one at account scope, and never the other environment's. A key stored for one environment alone therefore serves only that environment, and the gateway refuses the other environment's calls through the upstream. declare_upstream accepts such a key, and its detail names the environment whose calls are refused.

To fix it, store a value for that environment under the same name. To use one key for both environments instead, store it at account scope under a new name, then declare the upstream again with that name. The name must be new because a name stays at the scope where it was first stored.

3. Declare the upstream

Call declare_upstream with these members:

Member Required What it contains
name Yes 1 to 64 letters, digits, hyphens, or underscores, beginning with a letter or digit.
base_url Yes A public HTTPS URL on a host of your own or your provider's, not on an outbound mail port (25, 465, 587, or 2525).
credential_name Yes The name the key is stored under.
auth_header Yes The Hypertext Transfer Protocol (HTTP) header the API reads the key from.
application Yes The identifier of the one application the upstream belongs to. Without it, the call is refused 400 application_required.
auth_format No The header value, containing {value}. Bearer {value} adds the bearer prefix; the default {value} sends the key alone.
token_shape No gemini or anthropic, to count the tokens in each response.
settings No The settings an unchanged SDK reads in a deployed copy: base_url, and optionally key and base_path. A revision that omits it keeps the current settings, and null removes them. An unchanged SDK describes them.

An upstream declared here needs no entry in the manifest's egress list. The gateway dials it from the platform's edge, and the egress list names only the hosts the application's own process dials.

Every upstream belongs to one application, and it cannot be unbound or moved to another application. It ends only with undeclare_upstream, or with its application or your account (End an upstream). list_upstreams lists the account's upstreams, and the action reference gives the full rules.

The base URL must not name one of the platform's own hosts, or it is refused 400 refused_platform_host, at the declaration and at each call. The platform's own hosts are its vaults, storage accounts, container registries, and service hostnames. They also include the management and sign-in endpoints of the cloud provider the platform runs on. The check is by exact hostname, so your own vault, storage account, or registry at the same provider works like any other upstream.

Both hostnames of the issue service, which stores applications' issue-tracking spaces, are among the platform's own hosts. So no declared upstream reaches the issue service: an application reaches its own space through the platform's issue-tracking upstream (step 5).

The host must also resolve to public addresses. At each call, the gateway looks up the host and refuses the call 403 when any address it finds is private, loopback, link-local, reserved, or in the platform's own range. The refusal is one of egress_private_address, egress_loopback_address, egress_link_local_address, egress_reserved_address, or egress_platform_address. Its body gives the class and the host, not the address the host resolved to. The gateway refuses before it reads your key and before any request leaves it.

The gateway then connects to the address it checked. A host that resolves to no address is refused 502 upstream_unreachable. An upstream on a private network cannot be reached through the gateway. A base URL whose host is a non-public IP address is refused at the declaration.

Declare in the manifest

The manifest's optional upstreams member declares the same upstream with the code. Its keys are upstream names, and each entry takes the members in the table above except application. Each submit_manifest declares every entry for the application it is submitted for, from that moment.

With its upstreams in the manifest, the application is described by one file. Re-creating it, or copying it into another account, then takes the manifest, a store_secret for each key, and a deploy. Author the manifest shows the member.

Every rule in this step applies to an entry too. A submission that breaks a rule is refused manifest_invalid. Its line for the entry gives the entry's path and the refusal from Refusals that the same declare_upstream call would meet. A name another application of your account already uses is refused that way, naming that application. A clash that arises while the manifest is being recorded is refused by its own name instead, binding_refused or upstream_key_is_bound, with the manifest kept; Author the manifest says how to finish.

Where an upstream's running copies have a key setting, keep settings with its key in the upstream's entry. Otherwise the submission ends those keys in each environment, and its response says so. While the member lists an upstream, declare_upstream refuses to change it, manifest_owned_field. Change the entry and resubmit instead. Removing an entry leaves the upstream declared, and declare_upstream can change it again. No submission ends an upstream (End an upstream).

End an upstream

Call undeclare_upstream with the upstream's name to end it. The declaration is removed, and the name is free for a new one. Within the gateways' short cache interval, calls to the upstream are refused in both environments, and its egress keys stop working. That includes calls from any running copy that still calls it, whether or not that copy held a key.

The response's egress_keys_ended counts those keys, and detail lists the environments whose deployed copies lost one, and those whose serving version or deploy in flight was given the upstream's settings. The stored key stays in custody, and delete_secret can then delete it where nothing else uses it. A call that fails ends nothing, and calling again ends the upstream.

Only this call, or deleting the application or your account, ends an upstream. Submitting a manifest without the entry leaves it declared, so a running version that still calls it keeps working. While the manifest lists the upstream, undeclare_upstream is refused manifest_owned_field. So end one in this order: remove the entry and submit the manifest, then deploy and promote the version that no longer calls the upstream, then call undeclare_upstream.

A name that is not declared returns unchanged. To bring an ended upstream back, declare it again with declare_upstream, then deploy or promote each environment so its copies get fresh settings and keys.

4. Call through the gateway

The backend calls /egress/v0/<upstream>/<path> on the platform's origin under its platform credential. The gateway then:

  1. reads the key stored under the declaration's credential name, for the request's environment, and otherwise at account scope, never for the other environment;
  2. puts the key in the declared header and forwards the request to the upstream;
  3. returns the upstream's status and body, streamed responses included, within the gateway's limits.

The request's environment is the one the platform credential belongs to. A call under a minted token names the environment in the header x-turnzero-cloud-environment, and without it is refused 400 environment_required.

The upstream receives your request's method, path, query string, body, and headers, minus these:

Removed Why
Your platform credential's Authorization header It is your credential to the platform, not to the upstream.
Host, Accept-Encoding, Content-Length, the hop-by-hop headers such as Connection, and every header your Connection header names The gateway sets them again for its own request.
Expect The gateway responds to a 100-continue itself before it reads your body. A client that sends one before a large body, as curl does by default, works normally.
Any header you sent under the declaration's authentication header name Only the stored key arrives under that name.
Forwarded, Via, X-Real-IP, X-Client-IP, X-Cluster-Client-IP, True-Client-IP, X-Original-Forwarded-For, every header beginning x-forwarded- or x-envoy-, and the headers the hosting infrastructure's edge and ingress add The platform's edge or a proxy adds them on the way to the gateway.
Every header beginning x-turnzero-cloud-, the environment header among them, and every header under an earlier name of the platform's own header family They are addressed to the platform.
Every platform cookie in your Cookie header. Its name begins turnzero_cloud_ in any letter case, with or without a __Host- or __Secure- prefix. Or it has a name the platform gave the same cookies before They contain a builder's session or your end users' realm sessions.

So no upstream receives the caller's network address, the platform's internal identifiers, or a session the platform issued. A header you send under one of those names is removed too, because the edge adds to the value you sent. To pass a value such as an end user's address, use a header name that starts with a prefix naming your own application, for example x-myapp-end-user-address.

Every other header you send arrives unchanged, your own cookies included. Headers you do not send are filled with fixed defaults that identify nothing about you: Accept, Accept-Language, Accept-Encoding, User-Agent, and Sec-Fetch-Mode.

An upstream's response has the header x-egress-upstream: <name>. A refusal from the gateway itself, before the upstream is reached, uses the platform's error format without that header. The gateway limits the request size, the response size, and the upstream's time, and refuses each by name. Handle a response that stops after streaming has begun.

A deployed backend finds what the call needs in its settings. TURNZERO_CLOUD_GATEWAY_URL contains the gateway's origin where the platform gives a separate one. Otherwise the call uses TURNZERO_CLOUD_API. TURNZERO_CLOUD_TOKEN contains the platform credential, sent as the bearer. Deploy an application lists every setting a deployed container receives.

A local run tests the stored key before any deploy. Run the application on your machine with the environment file the provision line writes (Run locally). Its calls go through the public gateway under the development platform credential, which applies the development key. So a wrong credential name, header, or key shows up before the first deploy. The key must be stored at development or account scope for this.

The sample below makes the raw call for an upstream declared as weather. It tells the upstream's response apart from the gateway's own by the marker header. The Egress package's client in step 5 does the same for you. No test checks this sample.

const upstream = 'weather';
const origin = process.env.TURNZERO_CLOUD_GATEWAY_URL ?? process.env.TURNZERO_CLOUD_API;
const response = await fetch(`${origin}/egress/v0/${upstream}/v1/forecast?city=Austin`, {
  headers: { authorization: `Bearer ${process.env.TURNZERO_CLOUD_TOKEN}` },
});

if (response.headers.get('x-egress-upstream') === upstream) {
  // The upstream's own answer, whatever its status.
  const forecast = await response.json();
  console.log(response.status, forecast);
} else {
  // No marker: the gateway refused the call, in the platform's error format.
  const refusal = await response.json();
  throw new Error(`${refusal.error}: ${refusal.detail}`);
}

Under the platform credential the call needs no environment header, because the credential fixes the environment. A 4xx or 5xx response that has the marker is the upstream's own error, not the gateway's.

An unchanged SDK

A provider's own SDK can call through the gateway with no code change. Declare the upstream with settings, naming the settings the SDK reads for its base URL and its key. From the next deploy, promote, or restart_application, the application's deployed copy receives them:

  • The base_url setting contains the gateway's address for the upstream. That is TURNZERO_CLOUD_GATEWAY_URL where the platform gives a separate origin, otherwise TURNZERO_CLOUD_API, then /egress/v0/<upstream>, then base_path.
  • The key setting contains an egress key, kept secret. The platform creates it for this upstream and this deployed copy alone. The gateway accepts it on this upstream's route and swaps it for your stored key, so the stored key never enters the copy.

The OpenAI SDKs read OPENAI_BASE_URL and OPENAI_API_KEY, and add paths such as /chat/completions, so their upstream takes base_path /v1. The Anthropic SDKs read ANTHROPIC_BASE_URL, and send ANTHROPIC_AUTH_TOKEN as a bearer. Name that one as key, not ANTHROPIC_API_KEY, and declare the upstream with auth_header x-api-key.

The declaration below is for the OpenAI SDKs. No test checks this sample.

{
  "name": "openai",
  "base_url": "https://api.openai.com",
  "credential_name": "OPENAI_KEY",
  "auth_header": "Authorization",
  "auth_format": "Bearer {value}",
  "application": "<application id>",
  "settings": { "base_url": "OPENAI_BASE_URL", "key": "OPENAI_API_KEY", "base_path": "/v1" }
}

An egress key works on its own upstream's route and nowhere else. Every other route refuses it egress_key_not_admitted. It ends with the copy that has it: the next deploy, promote, or restart creates a fresh one, and the old one stops once the previous copy is gone. So an SDK that ignores its base URL sends the provider only a key that reaches this one route, for a while.

A revision that omits settings keeps them and every key. A revision whose settings is null, or has no key, ends the upstream's keys in each environment. Each key stops within the gateways' short cache interval, not at once. The response's egress_keys_ended counts them, and detail lists the deployed copies that lost one. Those copies' SDK calls through the upstream are refused until you declare it again with key and each environment's next deploy, promote, or restart_application gives its copy a fresh key.

The settings reach deployed copies only. For a run on your machine, set the two by hand in the environment file the provision line writes. Set the base URL first, to that file's TURNZERO_CLOUD_API, then /egress/v0/<upstream> and base_path. Then set the key to the file's TURNZERO_CLOUD_TOKEN, the development platform credential. That credential reaches every development upstream of the application, not this one alone.

Never run with the key setting alone. An SDK that has no base URL setting calls the provider's own host, and sends it the development platform credential.

declare_upstream refuses a settings name the platform sets, or one another upstream of the application or a manifest binding already uses, invalid_request. Each name is an upper-case letter, then upper-case letters, digits, or underscores, ending in a letter or a digit.

5. Take the Egress package

The Egress package's client makes the call for you. It sends the upstream's own path through the gateway, using a function your application supplies to add the credential. It returns the upstream's response unchanged when the marker header is present. It throws every response from the gateway itself as a typed refusal whose kind says what to do. It reads a streamed response frame by frame.

A call under the plan's included AI allowance needs no stored key. The AI Allowance package calls the platform's own upstream, ai-allowance, through the same gateway. The platform adds its own key, and the call is counted in token units against the plan's allowance.

You choose the outbound path per call. A call through AI Allowance draws the allowance. A call through your own declared upstream uses your stored key and draws nothing, so one backend can use both.

A call to the application's own issue-tracking space goes through the platform's second upstream, issue-tracking, under the application's platform credential or a token for the application. The gateway reads the token of the space the application's calls reach and presents it to the issue service, and never returns it in a response. No plan quantity limits these calls, and no usage state refuses them (Plan and usage).

An application's own space is shared by both environments, so the gateway applies three rules to its calls. It allows only the calls of the report grant level, which files reports and reads them. It stamps each report with the calling environment and the version that environment runs. It puts development: ahead of every actor id a development call names. From development, a read must name its actor, and a write may not name an issue by its id. Issue Tracking describes the package's client and these rules.

An application whose spaces were created one per environment keeps them, and the gateway allows its calls at the report grant level too. A call that updates, comments on, relates, settles, or signals an issue is refused issue_tracking_call_refused, as is one that opens or closes an ask, erases an actor, or exports the space.

A manifest can instead bind the application to an issue space of your account's own. The gateway applies the same three rules there, at the grant level the manifest gives. The binding works while your account has Turn Zero Blueprint. Once the account no longer has it, the gateway refuses each call with blueprint_required.

6. Rotate the API key

Replace the key with its issuer, then call rotate_secret under the same name and environment. The gateway reads the key on every call, so the next call uses the new key, with no rebuild or redeploy.

A changed declare_upstream takes effect on the next call after the credential interval Sign-in, sessions, and tokens states, because the gateway keeps the declaration for that long.

7. Declare an open host

A host the application reaches with no stored key goes in the manifest's egress list: a public data feed, a webhook target, or a provider whose key the end user sends with each request. The application reaches it through the HTTPS tunnel: an ordinary HTTPS connection through the platform's proxy, on port 443, to a public address.

Use a client the tunnel covers, such as the runtime's global fetch. Other clients need a proxy agent built from HTTPS_PROXY. Egress firewall and request limits lists the covered clients, the observe and enforce modes, and the tunnel's refusals.

8. Read the records

The two routes keep different records:

  • The gateway stores usage rows per upstream and account: bytes for each call, and tokens when the declaration sets token_shape. read_usage counts these bytes under data transfer.
  • The tunnel records each connection when it closes: the host and port, the bytes each way, the duration, and the outcome, with the reason for a refusal or a cut. read_usage counts the bytes sent to the host under data transfer.

read_logs with source: "egress" returns the tunnel's connection records, never the gateway's usage rows. The proxy writes one egress_establishments record per host, port, outcome, and declared flag each minute. It writes each refusal as its own record, naming the host.

In observe mode, a connection to a host the manifest does not list is allowed and recorded. In enforce mode, it is refused egress_undeclared, and the record gives the manifest edit for a hostname.

9. Record the declaration in your project

Consider noting each upstream beside the code that calls it: the API, the gateway path, and the credential name, never the key itself. The platform requires no particular document or format.

Expected result

  • store_secret returns the name, the scope, and stored: true, never the value.
  • declare_upstream returns the upstream, with its name, base_url, credential_name, auth_header, auth_format, token_shape, application, and settings. Its outcome is created for a new upstream or revised for a changed one, with a detail sentence. When the key is stored for one environment alone, detail also names the other environment, whose calls are refused until it has its own key under that name.
  • Where the declaration includes settings, the response's settings lists the settings a deployed copy reads and the path its base URL ends with, and detail says when they arrive. Every detail also gives the settings the OpenAI and Anthropic SDKs read, and points to the section An unchanged SDK. The response's page is the address of this page.
  • Where a revision's settings is null or has no key, the response includes egress_keys_ended, the count of egress keys it ended. Its detail lists the deployed copies that lost one and how to give them a fresh key.
  • list_upstreams then lists the upstream with its settings, created_at, and updated_at.
  • undeclare_upstream returns the upstream as it stood, outcome removed, and egress_keys_ended. A name not declared returns outcome unchanged and upstream null.
  • A submit_manifest whose upstreams member names the upstream returns upstreams rows, one per upstream and environment, each saying whether its key is stored there. A deploy whose environment lacks a key lists the upstream in its version's outcome.credentials_missing.
  • A backend call to /egress/v0/<upstream>/<path> returns the upstream's own status and body with the header x-egress-upstream: <name>. The application's process never had the key, unless the upstream sent it back.
  • For an open host, read_logs with source: "egress" shows the host's egress_establishments rows with the declared flag set, and filter: "undeclared" returns no row for it.

Refusals

A refusal names the action or the route, gives its cause in detail, and changes nothing. A gateway refusal made before the upstream is reached has no x-egress-upstream header. A refusal that ends a stream already begun arrives as the final event egress_error inside the marked response.

The rows are the refusals of the actions this guide calls, then the gateway's, then the allowance route's, then the issue-tracking route's, then the tunnel's one refusal this guide describes. The last two rows are the limits per plan. Egress firewall and request limits lists the tunnel's other refusals and describes the limits.

Refusal Status Cause Remedy
invalid_request 400 A declare_upstream member breaks the rules in step 3, and detail names it. On the issue-tracking upstream, the call's body broke one of the rules step 5 states for a shared space. Correct the named member and declare again. Store a secret lists the refusals of the storing actions. On the issue-tracking upstream, change the body as detail says.
reserved_upstream_name 409 name is ai-allowance or issue-tracking, the platform's own upstreams. Choose another name.
credential_not_in_custody 409 The credential_name is not stored at a scope the upstream can use, at the declaration or at the call. At a call, detail says when it is stored for the other environment alone. Where the key was deleted while declare_upstream recorded the declaration and it could not undo its own write, the detail says the declaration remains as the call wrote it. Store the key under that name with store_secret, as Store a secret describes, then retry. If detail mentions the other environment, store this environment's own key under the same name at this environment's scope. A key both environments share can instead be stored once at account scope under a new name, with the upstream declared again naming it. Omitting settings from that declaration keeps them. Where the detail says the declaration remains, store the key again, or declare the upstream again with a stored name.
upstream_key_is_bound 409 declare_upstream named a credential that a setting binds: in the manifest's settings, in the running copy of either environment, or in a deploy, promote, or redeploy in flight to one. Its value enters the copy, and a key the gateway applies never does. The detail names the setting and where it is bound. submit_manifest gives it too when such a binding is recorded while the manifest is being recorded; the manifest stays recorded. Where declare_upstream could not undo its own write, the detail says the declaration remains and how to end it. Store the upstream's key under another name and declare with that name. Or remove the binding from the manifest's settings, resubmit the manifest, deploy or promote each environment whose running copy has it once any deploy, promote, or redeploy in flight has ended, and declare again. Omitting settings from that declaration keeps them.
application_required 400 declare_upstream named no application. Name the application the upstream belongs to; list_applications returns the identifier.
binding_refused 409 An upstream that belongs to one application was declared again for another. submit_manifest gives it when another application declares an entry's name while the manifest is being recorded; the manifest stays recorded. Keep the current application, or declare a new upstream under another name. To use the name for another application, end the existing upstream with undeclare_upstream first.
manifest_owned_field 409 declare_upstream would change an upstream the application's manifest lists in upstreams; detail lists each field. Or undeclare_upstream would end one. Change the entry in the manifest and resubmit it with submit_manifest. Or remove the entry, resubmit, and declare again. To end the upstream, remove the entry, resubmit, and promote the version that no longer calls it, then call undeclare_upstream again.
name_bound_to_realm 409 The credential name belongs to a realm's work-account client secret or its Sign in with Apple signing key, which no upstream can use. Store the upstream's key under another name and declare with that name.
no_such_application 404 application names no application of your account. Use the identifier list_applications returns.
scope_fixed 409 store_secret or rotate_secret named a scope the name may not move to. That is the account scope for a name at an application's, another application's scope, or an application's scope for a name at the account scope. The application's other environment is never refused. Use the scope the refusal's scope, application, and environment members give, or store the key under a new name.
platform_minted_name 409 declare_upstream or store_secret named a name the platform created for an application: database-<application id>, credential-<application id>, or issue-tracking-<application id>. rotate_secret refuses the first two outside the development scope and the third on every scope. At a call, the gateway refuses the same way an upstream declared under such a name before declare_upstream refused it, and reads no key. Store your key under another name and declare the upstream with that name. Omitting settings from that declaration keeps them. The turnzero-cloud command's provision line, which submit_manifest returns for local_run, renews the database and platform credentials, calling rotate_secret on the development scope; through your AI tool, rotate_secret refuses them local_route_required. No request rotates the issue-tracking space's token.
local_route_required 409 Your AI tool called rotate_secret as a Model Context Protocol (MCP) tool for database-<application id> or credential-<application id> on the development scope. The tool returns no credential value. A call for a key of your own is never refused this way: it returns the command line that sends the key. Call submit_manifest from your tool with the application, its manifest, and local_run: true, and run the line it returns, which makes the call over the HTTP API. Then deploy development again where development is turned on.
never_deployed 409 read_logs named an environment with no deployed version, or a container read named one with no compute and no record from a failed first health check. Deploy to that environment first, or read the local stream. Where the detail names development's kept lines, read again with environment set to development.
cell_not_configured 503 read_logs: the platform's log store for the application is not set up. Retry later, and report it if it persists.
environment_required 400 A gateway call under a minted token or an account-wide credential had no x-turnzero-cloud-environment header, or one naming neither environment. Send the header with development or production, or call under the application's platform credential.
environment_mismatch 403 A call under an application's platform credential or an egress key named another environment in the header. Drop the header, or name the credential's own environment.
unknown_upstream 404 The path has no upstream segment, or its upstream segment contains a NUL character (U+0000). Call /egress/v0/<upstream>/<path> with the declared name first.
undeclared_upstream 404 Your account declares no upstream by that name. Declare it with declare_upstream or in the manifest's upstreams member.
upstream_scope_refused 403 The credential cannot use this upstream, which belongs to another application or takes no application-scoped token. Call under the application's platform credential or an account-wide credential.
egress_key_not_admitted 403 An egress key was sent anywhere but its own upstream's route, or to that route after its declaration dropped key. Send the key only as the bearer of calls under the base URL its setting names. For another upstream, declare it with its own settings.
refused_base_url 400 At the call, the declared base URL's scheme, host, port, or address type is refused. Declare the upstream again with a public HTTPS base URL. Omitting settings from that declaration keeps them. A base URL on a mail port (25, 465, 587, or 2525) stays refused. Send mail through a mail provider's HTTPS API instead, declared as an upstream.
egress_private_address, egress_loopback_address, egress_link_local_address, egress_reserved_address, egress_platform_address 403 At the call, the host resolved to a non-public address of that type; the body gives the class and the host, not the resolved address. Declare the upstream again with a host that resolves to public addresses only. Omitting settings from that declaration keeps them.
refused_platform_host 400 The base URL's host is one of the platform's own; detail names it. Declare the upstream against a host of your own.
credential_unreadable 502 The key is stored, but the gateway could not read it. Retry. If it persists, store the key again under the same name and report it.
request_too_large 413 The request is larger than the gateway allows. Send a smaller request body.
response_too_large 502 The upstream's response is larger than the gateway allows; a stream stops with this final event. Ask the upstream for less, using its paging where it has any.
upstream_timeout 504 The upstream took longer than the gateway allows; a stream stops with this final event. Retry, or ask for less work per call.
upstream_unreachable 502 The connection to the upstream failed; detail gives the error code, such as fetch failed (ENOTFOUND). Retry, and check the base URL and the upstream's own status.
authentication_required 401 The call sent no credential, or one that matches no account. Send the application's platform credential or a minted token.
allowance_application_required 403 An allowance call used an account-wide credential, such as a session or an account-scoped token. Call under the application's platform credential, or a token for the application with the environment header.
allowance_busy 429 The application already has the most allowance calls in progress that the platform allows. Retry after one of them ends.
allowance_exhausted 429 This UTC month's allowance is used up; the refusal includes resets_at, used, and quota. Wait for resets_at, move to a larger plan, or call your own upstream. Retrying this month is refused and counted each time.
allowance_feature_refused 400 The body is not JSON, or asks for tools, cached content, or output other than text. Ask for text generation only, or use your own key through a declared upstream.
allowance_path_refused 403 The request is not a POST to the text-generation method of a model the platform allows. Call through the AI Allowance package's generateText function, with an allowed model.
allowance_unavailable 503 The platform has no key for the allowance yet. Call your own declared upstream, and report it if it persists.
blueprint_required 403 The call's environment is bound to an issue space of your account's own, and the account does not have Turn Zero Blueprint. During the private beta, Blueprint is given by invitation. Once the account has it, repeat the call.
issue_tracking_application_required 403 An issue-tracking call used an account-wide credential, such as a session or an account-scoped token. The space belongs to one application. Call under the application's platform credential, or a token for the application with the environment header.
issue_tracking_call_refused 403 The gateway does not allow this issue-tracking call. It allows only the calls the Issue Tracking package marks as an application's own, and never the space's deletion. A deployment reaches an application's own space at the report grant level, which files and reads reports and never changes an issue. Make only the calls the Issue Tracking package's client makes for an application, within its grant level. Issue Tracking lists the calls the report level refuses.
issue_tracking_not_provisioned 409 No issue-tracking space exists for the call's environment. Declare the manifest kind issue_tracking and resubmit the manifest. The first submission creates one space for both environments. An application that has a space for each environment gets its production space at the first promote, or at its first production deploy where it has one environment.
issue_tracking_unavailable 503 The platform runs no issue service, or the gateway could not read the space's token. Retry. If it persists, report it.
plan_quantity_unset 409 An allowance call ran under a plan whose gemini-flash-allowance quantity is not set. Wait for platform staff to set the quantity, or move to another plan. read_plan_quotas shows it as a null quantity.
egress_undeclared 403 In enforce mode, the tunnel's target host is not in the manifest's egress list. Add the hostname to the list, resubmit the manifest, and deploy. For an IP address, list the hostname it serves.
egress_connections_capped 429 The tunnel refused a new connection. The application opened more connections in this UTC minute than its plan allows on one proxy replica. On the runtime harness, your code receives this name and the 429, without the figures. The refusal's record under read_logs source egress includes the count and the limit. Wait for the next UTC minute, then connect again. Reuse connections instead of opening one per request, or move to a larger plan with set_plan.
egress_daily_bytes_capped 429 The application has sent and received more bytes today, in UTC, than its plan allows, through the tunnel and its own upstreams. The gateway refuses the call and reads no key. Its body includes count, today's bytes, with limit and resets_at. The tunnel refuses new connections and closes open ones. On the runtime harness, your code receives this name and the 429, without the figures. Wait for resets_at, the start of the next UTC day. read_usage gives it in the egress_limits member, with today's bytes and the limit. Or move to a larger plan with set_plan.