Add a saved provider connection
Store a provider key once as a named connection, check it against a model, activate it, grant its use, and replace it.
On this page
Your developers need traffic to reach a provider. They don’t need the provider’s API key. Add it once as a named connection in the workspace, and they pick it from a list by name.
Two facts before the first step. A saved connection’s secret is stored encrypted and can’t be read back: later reads show its label, its version number and its status, and nothing derived from the secret itself — no masked hint, no last four characters, not even a length. Your applications hold a TokenTriage runtime key and no provider secret at all.
You need connection_manager or workspace_admin on the workspace. Who can do what in your
company is where those grants come from.
Availability. The Provider connections page is wired to the operations described here: add, replace the key, check it, activate it, rename, disable, and grant or revoke its use. Two things this page tells you plainly rather than promising: a disabled connection cannot be turned back on, and the cost of a check is reported after the call rather than estimated before it.
1. Add the connection
Provider connections belong to a workspace, and there is one page for them. Reach it from the application’s Connect this application action, or from the workspace address directly. Nothing is pre-filled from the application you came from.
Four fields sit in Add a provider connection, and you fill them in one form:
- Connection name. Something a colleague can pick out of a list, such as
IT / Development / Provider X. Names are what a gateway’s connection picker shows, so include the environment. - Provider. One supported provider per connection, chosen from a list. The destination address is derived from the provider, so there is no field for a URL: a stored key cannot be pointed at an arbitrary host.
- The provider account this key belongs to. Optional, and your own words. It is stored as you write it and is not checked against the provider, because an opaque key cannot prove which billing account it belongs to.
- Provider API key. A password field with no reveal control. It is stored encrypted, it is not shown again, and it is not sent back to your browser. Replace it if you lose it.
Behind that one form are two operations: create the row, then deposit the key into it. When the create succeeds and the key write is refused, the page says so in those words — the connection is on the server and holds no credential, and you deposit a key from Replace key on its row. Adding it a second time would leave you with two.
Saving grants nothing. The row’s own line says it: nothing can dispatch through the connection until you check the key with the provider, activate it, and grant it to an application and an environment.
2. Check the key, then activate it
Checking and activating live in the Replace key panel on the connection’s row, and that panel runs three steps in this order and no other.
- Store this key as a candidate. The version comes back as
candidate, written and not in use. - Check this key. Type a model the provider account is allowed to use into Model to check with, then check. This is a real request to the provider and it can be billed to you.
- Activate version N for this connection. Offered only when the check came back accepted.
The panel checks the version it wrote, and it is the only place a check can be started. So the key you deposited on the add form is checked by pasting it into this panel once more: that deposit is the next candidate version, and activating it is what puts a credential into use. A freshly added connection with nothing done here reads “No version has been activated”, which is accurate rather than a fault.
Four outcomes, and only two of them are a verdict on your key:
| What the page says | What it means | What to do |
|---|---|---|
| the provider accepted this credential | a request reached the provider with this key and the provider answered it | activate the version |
| the provider refused this credential | the provider rejected the key: wrong, revoked there, or not permitted to use the model checked | replace the key |
| the provider answered with an error | the request arrived and the provider failed it, which says nothing about your key | check again later |
| the key store could not be reached | your key was never unsealed and never presented, so nothing about it changed | retry once the key store answers |
Reading either of the last two as a bad key is what sends somebody to replace a working production credential at two in the morning, so the page names them apart.
Each check is recorded against the version it ran against, with the model, the time and the cost attributed to it. That cost prints as an integer of nano-USD, with a line saying a billion of them is one dollar. When no cost was attributed, the page says so and adds that this is not the same as the call having been free. Successful OpenAI checks use the provider’s complete token usage and the same price snapshot as the cost engine. Missing or inconsistent usage, an unavailable price, or a rate that would require an estimate leaves the cost unknown. Earlier unknown checks are not reconstructed from later checks. Keep their reserved exposure until it can be reconciled.
The credential in use does not move until you activate. Activation carries the revision you read, so two people activating at once get one winner and one refusal rather than a lost update.
You can leave and reopen the page after checking a candidate. Its result stays visible before activation, including a failed check or an unknown cost. Reading it does not send another provider request. The credential-version label distinguishes that result from the version currently in use.
An accepted check says one model answered once. It isn’t a claim that every model on that account works, or that a future quota will hold.
3. Let an application use it
Open Who may use it on the row. One grant names one application, one environment and one purpose, all three, and a connection with no grant serves nothing however healthy it is. Production is another grant you make explicitly, not a box that arrives ticked.
The purpose list is fixed in the product: application traffic, playground runs, comparisons, quality judging and connection checks. A purpose nobody wrote a resolver for is a grant nobody can honour.
Which connections a model choice may use on the same page answers the eligibility question for one application, environment, model and purpose. All four are required, because the server declines a question that leaves one of them open. The panel filters rather than annotates: a connection you may not use here is absent from the answer, not greyed out.
That panel reports; it does not bind. Binding happens in the gateway builder’s Provider
connections section, where each upstream slot carries a Connection for … picker offering only
the connections already granted for that application, environment and purpose. A slot nobody has
chosen for reads No connection bound, which is what connection_slot_unbound reports at release
time.
The saved configuration holds a reference to the connection. It holds no secret, so a configuration diff, an export or a screenshot of the builder carries nothing worth stealing.
One connection can serve several gateways in the same application when its grants allow it, so a three-gateway application needs one key rather than three.
4. Replace the key
Replacing a key is the same three steps as step 2, on a connection that already has an active version: store the replacement as a candidate, check it, activate it. The credential in use keeps working the whole time, so there is nothing to book a quiet evening for.
Existing TokenTriage runtime keys and deployed gateway releases keep working across a replacement. A release pins the connection and its provider account context, while each new dispatch resolves whichever secret version is active at that moment. Every attempt records the version that served it, so a cost figure stays attributable afterwards. The version the new one displaced is retired.
Two things replacing a key doesn’t do:
- It can’t revoke the old key at the provider. Do that in the provider’s own console once the overlap you want has passed.
- It doesn’t survive a rollback in reverse. Rolling a gateway back to an earlier release doesn’t revive a retired secret version, a disabled connection or a revoked grant.
If the replacement key belongs to a different provider account, project or destination, make a new reviewed connection instead. A different account is a different connection, and calling it a replacement would claim the checked behaviour carried over when it didn’t.
5. Rename it, or take it out of use
Rename changes the display name and nothing else: the stored key, its versions and every grant stay as they were. A deployment whose store implements no rename answers that it cannot do it at all, which is a fact about the build rather than a complaint about the name you typed, and retrying gets the same answer.
Disable stops new dispatch. The panel says what that costs before you confirm it: requests that would have gone through this connection start being refused, while the connection, its stored versions, its recorded checks and its grants are kept, and nothing that references it is edited. Cost reconciliation for work already done continues.
A runtime request refused by saved-connection custody returns custody_disabled with
retryable: false. Select an active eligible connection, save the binding and deploy the gateway
before sending another request. Requests already admitted can still finish and incur cost.
The initial design target is that a disable, a revocation or an access change stops new provider dispatch within 30 seconds, using invalidation plus an expiry bound rather than an open-ended stale refresh. That target has to be measured across workers, restarts and missed notifications before it is published as a guarantee, so treat it as the design intent for now.
Two limits on any revocation, whoever builds it. Requests already sent to a provider can’t be recalled, including streams in flight. And stopping the provider’s own key needs the provider’s console; disabling a connection stops TokenTriage from using it and nothing more.
When a request fails on its credential
Three failures look alike in a log and need different responses, so they’re distinguished on the wire.
| What you see | What happened | What to do |
|---|---|---|
| provider authentication failed | the provider rejected the active secret version | replace the key: store a candidate, check it, activate it. Check whether the key was revoked provider-side |
| key service unavailable | the wrapping-key service couldn’t be reached and no valid cached handle remained | retry. This is a dependency failure, not a bad key, and no paid call was made, so no provider charge exists |
| connection disabled | somebody disabled the connection, or a grant behind it was revoked | a disabled connection stays disabled: bind the model to another eligible connection, or add a new connection and grant it. A revoked grant can be granted again |
Two behaviours follow from keeping them apart. A credential failure isn’t reported as an ordinary model failure, so a retry loop can tell broken configuration from an outage. And TokenTriage doesn’t try a different key after an authentication failure: probing keys hides the broken configuration and multiplies the charges.
Why a failed decrypt and an unavailable key service are separate answers
Corrupt ciphertext, a wrong address context and a rejected provider key are configuration faults: a retry produces the same answer. An unreachable wrapping service is transient, and the answer is retryable.
A cached credential handle may serve a request during a wrapping-service outage, but only inside its bounded authority and key lifetime. A cache miss fails as a dependency error rather than falling back to a revoked version, another connection or the caller’s own key.
Saved connections and request-supplied keys are two modes
Storage claims only make sense per mode, so say which mode you mean.
Saved connection is the mode this page describes, and it is the one a gateway starts in. TokenTriage holds the provider key encrypted and attaches it for the upstream call. Your application sends its TokenTriage runtime key and no provider secret. A cross-provider fallback resolves that target’s own bound connection rather than forwarding one provider’s key to another.
Request-supplied is a separate mode, selected in configuration. The request carries two separately identified credentials, the TokenTriage key and a provider key, and the supplied provider value isn’t persisted. An ordinary call can’t switch into that mode by adding a header, because the source mode belongs to the gateway’s configuration and not to the caller.
Request-supplied mode isn’t a route to arbitrary multi-provider fallback or to durable jobs that poll later. Use saved connections for those targets, or the combination is reported as unsupported.
A blanket “no provider keys are stored” sentence describes request-supplied mode only. In saved mode, the accurate statement is that the key is stored encrypted, isn’t readable back through any API, export or log, and is used only for requests the connection’s scope permits.
Save a candidate with the CLI or SDK
Automation uses the same connection and version operations as the page. Give the management key
connections.manage on the workspace. Keep that key in TOKENTRIAGE_MANAGEMENT_KEY; never put
it or the provider credential in a command argument. Set these non-secret values for your company:
export TOKENTRIAGE_BASE_URL=https://app.tokentriage.com
export TOKENTRIAGE_ORG=org_YOUR_COMPANY
export TOKENTRIAGE_WORKSPACE=t_YOUR_WORKSPACE
Create the connection first. Its response contains the connection ID but no credential:
tokentriage cloud --base-url "$TOKENTRIAGE_BASE_URL" \
--org "$TOKENTRIAGE_ORG" --workspace "$TOKENTRIAGE_WORKSPACE" --json \
connection create --name "IT / Development / OpenAI" --provider openai
Set TOKENTRIAGE_CONNECTION to that response’s data.id. Store the key at the CLI’s masked prompt:
tokentriage cloud --base-url "$TOKENTRIAGE_BASE_URL" \
--org "$TOKENTRIAGE_ORG" --workspace "$TOKENTRIAGE_WORKSPACE" --json \
connection write-secret --connection "$TOKENTRIAGE_CONNECTION"
For a secret-manager pipe, add --secret-stdin to that command. Do not combine it with
--management-key-stdin: one input stream cannot carry both credentials.
The runnable Python example (sdk/python/examples/save_provider_connection.py)
and TypeScript example (sdk/ts/examples/save_provider_connection.ts)
use the SDK’s single-use ConnectionSecret wrapper. From a repository checkout with the SDK
installed, choose one:
# Python asks for a masked key when run interactively.
python sdk/python/examples/save_provider_connection.py
# TypeScript reads stdin. Here the file is an existing protected secret source.
npx tsx sdk/ts/examples/save_provider_connection.ts < /secure/provider-key
With TOKENTRIAGE_CONNECTION set, each program writes a new candidate on that connection.
Without it, the program first creates a connection named IT / Development / OpenAI; set
TOKENTRIAGE_CONNECTION_NAME to choose another name. Output contains only the connection ID,
version and state, for example:
{"connection":"pc_EXAMPLE","version":2,"state":"candidate"}
Saving a candidate makes no provider call and does not activate it. Return to Replace key to check that exact version, review its outcome and cost, then activate it as described above. A check is a separate paid action; an unknown cost is not zero. Grant the application and environment separately before use.
A credential write is not replayable: repeating it creates another version. If the response is lost, read the connection and its versions before another write. The example programs never retry it.
What this page doesn’t cover
- The authentication schemes each provider uses, and what a descriptor carries: Authentication schemes.
- Configuring upstream credentials in a self-hosted deployment, which is a different mechanism: How credentials are configured.
- Binding a connection to a model choice inside a draft, and promoting it: Configure, test and promote a gateway.