Console
Get started/Authentication

Authentication

Keys, both header styles, and secure browser signup.

/llms.txt

Every gateway request authenticates with a virtual key — one key that works on both wire surfaces, spends one org balance, and never touches a provider credential of yours.

Virtual keys

PropertyValue
Formattm_vk_ followed by 48 hex characters (24 random bytes)
Shownexactly once, at creation
Storedonly the SHA-256 hash, plus the first 12 characters so the console can label it
Spendsthe org's shared prepaid balance
Limitsper-key requests-per-minute; optional per-key and org-wide monthly spend caps
Warning

The raw key is displayed once and cannot be recovered — only its hash is stored. If you lose a key, disable it at https://app.routerplus.com/console/keys and create a new one.

Both header forms are accepted

The gateway reads your key from either standard header, on both surfaces:

HeaderConventionTypically sent by
Authorization: Bearer tm_vk_...OpenAIOpenAI SDKs; Claude Code via ANTHROPIC_AUTH_TOKEN
x-api-key: tm_vk_...AnthropicAnthropic SDKs via api_key

You do not have to match the header to the surface — x-api-key works on POST /v1/chat/completions and Authorization: Bearer works on POST /v1/messages. If both headers are present, Authorization: Bearer wins.

bash
# OpenAI surface, bearer auth
curl -s https://api.routerplus.com/v1/chat/completions \
  -H "Authorization: Bearer $TM_API_KEY" \
  -H "content-type: application/json" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'

# Anthropic surface, x-api-key auth
curl -s https://api.routerplus.com/v1/messages \
  -H "x-api-key: $TM_API_KEY" \
  -H "content-type: application/json" \
  -d '{"model":"claude-haiku-4-5","max_tokens":50,"messages":[{"role":"user","content":"hi"}]}'
Note

anthropic-version is not required on /v1/messages — the gateway pins its own version (2023-06-01) when it dispatches upstream, and forwards your anthropic-beta header when the serving provider speaks the Anthropic dialect — filtered to an allowlist (prompt-caching, token-efficient-tools, fine-grained-tool-streaming, interleaved-thinking, claude-code, oauth and computer-use betas); any other value is dropped, and the drop is named in x-tm-dropped-params. The one place anthropic-version changes behavior: sending it on GET /v1/models returns the Anthropic-shaped model list instead of the OpenAI list shape. Anthropic SDKs send it automatically; that is fine.

In the SDKs, the key goes exactly where the provider's own key would:

python
import os
from openai import OpenAI
import anthropic

openai_client = OpenAI(base_url="https://api.routerplus.com/v1", api_key=os.environ["TM_API_KEY"])
anthropic_client = anthropic.Anthropic(base_url="https://api.routerplus.com", api_key=os.environ["TM_API_KEY"])
# anthropic.Anthropic(auth_token=...) also works — that sends Authorization: Bearer,
# which the gateway accepts too.
typescript
import OpenAI from "openai";
import Anthropic from "@anthropic-ai/sdk";

const openai = new OpenAI({ baseURL: "https://api.routerplus.com/v1", apiKey: process.env.TM_API_KEY });
const anthropic = new Anthropic({ baseURL: "https://api.routerplus.com", apiKey: process.env.TM_API_KEY });

Getting a key

In the browser

Open Sign up to create an account, or Sign in if you already have one. Public signup and sign-in use Clerk. Complete the authentication and email-verification steps shown there. Use the same verified email as your existing marketplace account to keep its organization, keys and balance; switching sign-in methods does not create another trial. If you have an older marketplace account but no Clerk sign-in yet, use Sign up with that same email to connect it to your existing account.

For a new account, the first verified sign-in mints your first API key. Signup and email verification do not grant free credit. Add paid credits in Billing before making model calls. Save the key when it is shown after sign-in; the raw value is shown only once. At API keys you can create additional named keys, each likewise shown once. Browser sign-in does not change how your applications authenticate with existing tm_vk_ keys.

Public programmatic signup and the local magic-link submission route have been removed: POST /v1/signup and POST /login return HTTP 404. The ?magic=1 query parameter does not provide an alternate signup or sign-in form. Existing accounts do not need to be recreated; sign in through Clerk using their verified email.

A marketplace session lasts up to 7 days and remains subject to the linked Clerk session. To end it sooner, use Sign out in the account menu (the avatar in the app bar) or at the bottom of the console rail. Sign out ends this browser's session on the server. Other devices stay signed in.

Key rate limits

Each key has a request limit over a rolling 60-second window, enforced per key and shared across gateway instances:

Key originRequests per minute
First key shown after verified sign-in (trial)20
Created in the console, or with the identity API300
The console's managed Playground key60

Until your organization has bought credit, its customer keys and the organization as a whole run at the trial rate, 20 requests per minute, whatever a key's own limit says; the first purchase lifts it within seconds. Requests served only by your own provider keys (BYOK) and the managed Playground key are not held to it.

Exceeding it returns 429 with error_type: "rate_limit" and a retry-after header. Honor the header and retry; don't hammer. Your organization also has token-per-minute and concurrency limits; see Rate limits & spend caps.

Disabling a key

At https://app.routerplus.com/console/keys, each key row has a Disable action. Disabling is permanent — there is no re-enable in v1; create a new key instead. The gateway caches key rows briefly, so a disabled key can keep working for up to about five seconds before every request returns 401.

You can also cap a key without killing it: per-key monthly spend caps are set on the same page (Set cap), and the org-wide cap at https://app.routerplus.com/console/billing. A breached cap returns 429 insufficient_quota with the exact UTC reset time in both the message and the x-tm-cap-reset header.

What a 401 looks like

A missing, invalid, or disabled key gets 401 with error_type: "auth". The body is shaped for whichever SDK family is calling, so your SDK's native error class fires; the stable error_type rides alongside. Headers always include x-request-id, x-tm-error-code: auth and x-tm-error-origin: authorization.

OpenAI surface (/v1/chat/completions and every non-Anthropic path):

json
{
  "error": {
    "code": "auth",
    "message": "missing or invalid API key",
    "type": "invalid_request_error",
    "metadata": { "error_type": "auth", "origin": "authorization", "retryable": false }
  },
  "request_id": "..."
}

Anthropic surface (/v1/messages):

json
{
  "type": "error",
  "error": {
    "type": "authentication_error",
    "message": "missing or invalid API key",
    "error_type": "auth",
    "metadata": { "origin": "authorization", "retryable": false }
  },
  "request_id": "..."
}

Both SDK families raise their own AuthenticationError for these. The message is deliberately the same for every 401 cause — check, in order: the header form, the key value, and whether the key was disabled in the console.

Key hygiene

  • Keep keys in environment variables or a secret store; never commit them and never put them in URLs — the gateway only reads the two headers above.
  • Use one key per app or agent, so the console's per-key spend and caps tell you who spent what — and so disabling one key kills one integration, not all of them.
  • The console also mints keys of its own: one Playground key per organization, and one per Optimize search. They show on the keys page with a managed tag, are never displayed, and bill your credits like any key.

Next: Quickstart for browser signup and the first API calls, or Errors for the complete error_type table.

Markdown source for agents: /docs/authentication.md · index at /llms.txt