Skip to content

Providers and your own keys

A run makes model calls for three roles:

  • actor: the persona deciding each step.
  • judge: the observer that reads each session and merges the run’s findings.
  • reproduction: the check that asks whether a replayed failure supports the issue’s claim.

Saram bills the run itself (Usage and billing). Who bills the model calls depends on where they are made, and that is what the spec’s providers block says.

The calls are made on Who bills them How the spec names it
Your own provider key, stored with Saram Your provider, on your account with them A providers entry whose credential_ref is your key’s secret_ref
Your ChatGPT login, where Saram offers it OpenAI, within your ChatGPT subscription’s limits The providers block codex_status answers
Saram’s own credential Nobody separately No providers block

On the Free and Pro plans, every role is on your own provider. A spec with no providers block, or one that leaves a role on a Saram-held credential, is refused with a 422 that names the role:

plan 'free' pays for none of a run's model calls, and the actor role would make its calls on a Saram-held credential (this spec carries no providers block): bring your own key for it - POST /api/v1/credentials returns the credential_ref to name in providers.roles.actor

Saram’s own models run where your organisation has an agreement with Saram for them, or on an older plan that includes them. house_models: true in get_usage says Saram carries your model spend.

Every validate answer says who bills each role, in cost_ceiling.model_calls. A model call is never part of cost_ceiling.usd.

Supported providers, by the kind a credential is stored as:

anthropic, bedrock, openai, openrouter, azure, google, google-cloud, litellm

Store a key on the dashboard under Settings > Credentials, or with create_credential. The key needs the credentials:write scope. Build the body with jq, so the key stays out of your shell history:

Terminal window
jq -n --arg v "$ANTHROPIC_API_KEY" '{name: "anthropic", kind: "anthropic", value: $v}' |
curl -s -X POST https://api.saram.sh/api/v1/credentials \
-H "Authorization: Bearer $API_KEY" -H 'content-type: application/json' -d @-

It answers 201 with the credential’s metadata:

{
"id": "...",
"name": "anthropic",
"kind": "anthropic",
"secret_ref": "awssm://.../anthropic",
"created_at": "2026-10-23T09:00:00Z"
}
  • secret_ref is what the spec names as credential_ref. Only your organisation’s specs may name it; another organisation’s spec naming it is refused.
  • The value is never returned. No response anywhere has a value field, and the value is not logged. list_credentials answers metadata only.
  • name is lowercase letters, digits, _ and -, up to 64 characters. A name you already have is a 409.
  • value is the key as one string. For google-cloud it is the service-account JSON document.
  • There is no replace. To rotate a key, delete_credential it, then store it again under the same name.

The same route stores the other credentials a spec names: account for test logins (The run spec), webhook for a webhook’s signing key, and github for a GitHub token.

The smallest block is one entry, which every role uses:

"providers": {
"entries": {
"main": { "model": "anthropic:claude-opus-5", "credential_ref": "<your secret_ref>" }
}
}

To put roles on different models, name several entries, pick a default, and bind roles to entries in roles:

"providers": {
"entries": {
"fast": { "model": "openrouter:anthropic/claude-opus-5", "credential_ref": "<your OpenRouter secret_ref>" },
"careful": { "model": "anthropic:claude-opus-5", "credential_ref": "<your Anthropic secret_ref>" }
},
"default": "careful",
"roles": { "actor": "fast" }
}

Here the personas run on OpenRouter, and the judge and the reproduction check on Anthropic.

Field
entries Required, at least one. Each name is lowercase letters, digits, _ and -.
default The entry every role uses unless roles says otherwise. Left out with one entry, that entry.
roles actor, judge, reproduction: an entry name each.

An entry:

Field
model Required. <provider>:<model>, such as anthropic:claude-opus-5, openai:gpt-5.6, openrouter:anthropic/claude-opus-5, bedrock:us.anthropic.claude-opus-5, google:gemini-3-pro or azure:<deployment>. The prefix must be the kind the credential was stored as.
credential_ref The secret_ref of your stored key. Never the key itself: an entry has no field for one, and a field it does not list is refused.
base_url An endpoint in place of the provider’s public one: a LiteLLM or other OpenAI-compatible gateway, an Azure resource, a Bedrock VPC endpoint.
region bedrock: the AWS region.
project, location google-cloud: the GCP project and the Vertex location.
api_version azure: the Azure OpenAI API version.

A providers block wins over actor.actor_model and actor.judge_model.

Validate checks the whole block before anything starts: that every role resolves to an entry, that each prefix is a provider Saram resolves, and that every credential_ref is yours. A model Saram has no price for, which is either new or misspelt, is a warning rather than a refusal, so read warnings. For example:

run spec rejected: providers entry 'j' names model 'nope-provider:abc', and 'nope-provider' is not a provider this platform resolves: the prefix must be one of anthropic, bedrock, openai, openrouter, azure, google, google-cloud, litellm

Where Saram offers it, a run’s model calls can be made on your ChatGPT subscription instead of a provider key:

  1. codex_login (POST /api/v1/providers/codex/login) answers a verification_url and a user_code. Open the URL and type the code.
  2. Poll codex_status (GET /api/v1/providers/codex) until state is connected. It is none, pending, connected, expired or error, and it shows the plan, when the login expires and the subscription’s rate-limit use.
  3. Once connected, the same answer carries the exact providers block to paste into a spec, the credential’s name included. You never type one.

The login is yours alone. It serves only the caller that connected it, and only runs that caller starts: a spec on it with trigger.kind: ci is refused, as a subscription login serves runs a person starts. A 403 from codex_login means Saram does not offer ChatGPT logins to your account. codex_disconnect revokes the login and forgets it; your specs keep their providers block, so connecting again is all a later run needs.

Over MCP, get_provider_status answers the same state and block, and start_chatgpt_login starts the login (MCP tools).

Block Checked
Storing a key Not run for this page; the body and answer are the reference’s CredentialWrite and CredentialRecord
list_credentials 2026-10-23 against api.saram.sh (platform-api 0.9.0): metadata only, no value
The refusals for a ref that is not yours and for an unknown prefix 2026-10-23, POST /api/v1/runs/validate
The Free and Pro refusal without a providers block Not run: the organisation used for this page is not on Free or Pro. The sentence is the platform’s, for plan 'free'
The ChatGPT login 2026-10-23: GET /api/v1/providers/codex answered connected with a providers block; validate accepted that block with trigger.kind: manual and refused it with ci. codex_login not run