proxykey documentation
proxykey is a credential proxy. Your real API keys stay encrypted on the server; clients and AI agents use revocable virtual keys — passes — that are proxied to the upstream provider. This page is served by the service itself and is the authoritative integration reference.
Overview
You give proxykey a real provider key once. It is encrypted and never leaves the server. In return you issue passes (tokens starting with vlt_) and hand those to your apps, scripts, or AI agents. Each request goes to proxykey instead of the provider; proxykey validates the pass, injects the real key on its side, and forwards the call.
- The client never holds the real key — only a pass you can revoke in one click.
- Per-pass control — rate limits, IP binding, expiry, and request logging.
- Any HTTP provider — OpenAI-compatible LLMs, Anthropic, Telegram, or any custom REST API.
| Surface | Host | Purpose |
|---|---|---|
| Panel | app.proxykey.org | Web UI: add keys, issue passes, view logs |
| Proxy | api.proxykey.org | The request proxy — /p/<slug>/… |
| MCP | mcp.proxykey.org | Hosted MCP server for AI agents |
How it works
Three objects, in a chain:
| Object | What it is |
|---|---|
| Provider | The upstream API (e.g. openai, telegram-bot). Defines the base URL and how the credential is attached (header, path, query). |
| Secret | Your real provider key, encrypted at rest. Entered once, never returned by any API. |
| Pass (проходка) | A virtual, revocable token (vlt_<provider>_…) bound to a secret. This is what the client uses. |
# the request path
client ──(vlt_ pass)──▶ api.proxykey.org ──(real key)──▶ provider
│
validate pass, inject
real key, forward, stream back
The real key is decrypted in memory for the single request only. It is never logged, cached in plaintext, or sent back to the client.
Quick start
Store the real key
In the panel (app.proxykey.org) pick a provider and paste your real API key. It is encrypted immediately.
Issue a pass
Create a pass for that secret. The full vlt_… token is shown once — copy it.
Point your client at the proxy
Change the provider base URL to https://api.proxykey.org/p/<slug>/ and use the pass instead of the real key. Done.
# before — talking directly to OpenAI
curl https://api.openai.com/v1/models \
-H "Authorization: Bearer sk-...realkey..."
# after — through proxykey
curl https://api.proxykey.org/p/openai/v1/models \
-H "Authorization: Bearer vlt_openai_..."
The proxy endpoint
Send every request to the proxy host, keeping the provider's original path:
https://api.proxykey.org/p/<provider_slug>/<original_path>
https://api.proxykey.org/p/<slug>, and keep the entire rest of the path (including any /v1). Swap the real key for your vlt_ pass. Nothing else changes.
# only the host changes — the path stays
https://api.openai.com/v1/chat/completions → https://api.proxykey.org/p/openai/v1/chat/completions
https://api.groq.com/openai/v1/chat/completions → https://api.proxykey.org/p/groq/openai/v1/chat/completions
<provider_slug>selects the upstream (see Providers).- Everything after the slug is appended to the provider's real base URL. If that base already contains a version prefix (e.g. Hubris'
/v1), do not repeat it. - Your HTTP method, request body, query string, and non-auth headers pass through unchanged. Responses (including streamed SSE) are returned verbatim.
curl -s -o /dev/null -w "%{http_code}\n" \
https://api.proxykey.org/p/openai/v1/models
# => 401 (route is up; 401 = no valid pass supplied)
Authentication
A request carries its pass in one of three ways. proxykey checks them in this order:
- Authorization header —
Authorization: Bearer vlt_…(recommended for most providers). - X-Vault-Pass header —
X-Vault-Pass: vlt_…(when you cannot set Authorization). - In the URL path — for providers that natively carry the credential in the path (Telegram). Put the pass where the real token used to go; proxykey extracts it, strips that segment, and injects the real key. See Recipes → Telegram.
Providers & slugs
Each provider defines its upstream and how proxykey attaches the real key. The live catalogue is also returned by the MCP tool list_providers (note: it reports each provider's original base URL, not the proxy address — the proxy address is always …/p/<slug>/).
| Slug | Upstream | Auth model |
|---|---|---|
openai | api.openai.com | Bearer |
openrouter | openrouter.ai | Bearer |
groq | api.groq.com | Bearer |
together | api.together.ai | Bearer |
mistral | api.mistral.ai | Bearer |
deepseek | api.deepseek.com | Bearer |
openai-compatible | any OpenAI-style API (base URL set per secret) | Bearer |
anthropic | api.anthropic.com | x-api-key header |
hubris | api.hubris.pw/v1 | Bearer |
telegram-bot | api.telegram.org | token in URL path |
generic-rest | any REST API (base URL set per secret) | your custom auth |
Auth models
| Model | Where the real key goes upstream |
|---|---|
bearer | Authorization: Bearer <key> |
header | a named header with no prefix (e.g. x-api-key) |
query | appended as a query parameter |
path | substituted into a path template (Telegram: /bot<key>) |
- An OpenAI-style LLM → slug
openai-compatible. Set only its base URL on the secret, use the pass as the key. Two fields, done. - Any other REST API → slug
generic-rest. Set the base URL and pick how the key is sent (Bearer, a named header, or a query parameter). Anything over HTTPS works.
Recipes
OpenAI-compatible (openai, openrouter, groq, together, mistral, deepseek, …)
Take the base URL you already use and swap the host for …/p/<slug> — keep the rest, including /v1. Use the pass as the API key.
# python openai SDK — the host is the only change
client = OpenAI(
base_url="https://api.proxykey.org/p/openai/v1", # was https://api.openai.com/v1
api_key="vlt_openai_...",
)
# same idea for the others — keep each provider's own path
# groq → …/p/groq/openai/v1
# openrouter → …/p/openrouter/api/v1
# together → …/p/together/v1
# mistral → …/p/mistral/v1
# deepseek → …/p/deepseek
Providers whose base already includes /v1 (e.g. hubris) accept both shapes: the proxy collapses a duplicated /v1 boundary, so …/p/hubris/v1/chat/completions and …/p/hubris/chat/completions hit the same upstream.
Anthropic
POST https://api.proxykey.org/p/anthropic/v1/messages
Authorization: Bearer vlt_anthropic_...
# proxykey injects the real key as the x-api-key header upstream
Telegram
Telegram carries its token in the URL path. You do not restructure anything — keep your URL shape, change only the host, and put the pass where the bot token used to go.
https://api.telegram.org/bot<TOKEN>/sendMessage
# becomes — change host + use the pass instead of the token
https://api.proxykey.org/p/telegram-bot/bot<vlt_telegrambot_...>/sendMessage
No header, no path rewrite. Most Telegram SDKs let you set the API base host and the bot token — that is all you change (host → …/p/telegram-bot, token → your vlt_ pass). Alternative: drop the /bot<…> segment and send the pass in Authorization: Bearer against …/p/telegram-bot/sendMessage. Both work.
Telegram libraries (aiogram, telebot, grammY)
These libraries validate the token format <digits>:<hash> before the first request — a bare vlt_… pass fails their check. Prefix the pass with your bot id and a colon; the proxy accepts the composite form and ignores the digits:
# aiogram 3.x — two changes, zero client-code hacks
from aiogram import Bot
from aiogram.client.session.aiohttp import AiohttpSession
from aiogram.client.telegram import TelegramAPIServer
session = AiohttpSession(
api=TelegramAPIServer.from_base("https://api.proxykey.org/p/telegram-bot")
)
bot = Bot(token="8759668576:vlt_telegrambot_...", session=session)
# ^ your bot id + ':' + the pass — passes the library's format check
File downloads work through the same host: …/p/telegram-bot/file/bot<token>/<file_path> is proxied to Telegram's /file/bot<real>/…. Long polling (getUpdates?timeout=50) is safe — the proxy's upstream budget is 300s.
generic-rest (any custom API)
Configure the secret with a custom base URL and auth model. Then call:
https://api.proxykey.org/p/generic-rest/<your path>
Authorization: Bearer vlt_genericrest_...
Passes
A pass is a revocable token bound to one secret. You can issue many passes per secret (one per app, per agent, per environment) and control each independently.
| Control | Meaning |
|---|---|
| Rate limit | Requests per minute (rpm) and per day (rpd). Exceeding returns 429 with Retry-After. |
| IP binding | auto (locks to the first IP that uses it), manual (a fixed list), or off. Mismatch returns 403. |
| Expiry | Optional expiry timestamp; after it the pass returns 401. |
| Body logging | Off by default. When on, capped, redacted request/response body previews are stored for that pass. |
Lifecycle
- Rotate — mint a fresh token with the same settings; the old token stops working immediately.
- Revoke — disable the pass at once (proxy cache invalidated). Further requests get
401 pass_revoked. - Rebind IP — clear the learned IP so the next request re-binds.
The full vlt_… token is shown only once, at creation or rotation. proxykey stores only its hash and can never display it again.
Pending passes
An AI agent can issue a pass before the real key exists, so it never has to touch the key itself. Such a pass starts in status pending_secret and is blocked — the proxy returns 409 original_key_required — until a human supplies the real key.
The agent calls create_pending_pass (via MCP). A pass token is returned; the underlying secret has no value yet.
A human opens the pass in the panel, pastes the real provider key into “Original key required”, and clicks Activate.
The key is encrypted, the pass flips to active, and traffic flows. You can write integration code before activation — it starts passing once the key is set.
MCP for AI agents
proxykey ships a hosted Model Context Protocol server so an AI agent can manage passes on your behalf — without ever seeing a real key.
Connect
Issue an MCP token (mcp_…) in the panel, then point your client at the hosted Streamable-HTTP endpoint:
URL: https://mcp.proxykey.org/mcp
Auth: Authorization: Bearer mcp_...
The panel's MCP → Quick Connect gives ready snippets for Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Cline, and Codex CLI.
Tools
An MCP token is scoped to pass management only:
list_providers,list_secrets— catalogue & secret metadata (never values)create_pass,create_pending_pass— issue passesupdate_pass,rotate_pass,revoke_pass,rebind_pass_ip— manage themget_pass_stats,get_pass_logs— usage & auditget_manual_secret_setup— returns the panel URL where a human enters a real key
Security model
- Envelope encryption. Every secret is sealed with AES-256-GCM under a per-secret data key, which is itself wrapped by a master key. The secret's identity is bound into the encryption (AAD), so ciphertext cannot be moved between records.
- Keys never leave. A real key is decrypted in memory for one request and dropped. It is never returned by any API, cached in plaintext, or written to logs.
- Log redaction. Request logs store method, path (without query), status, latency and byte counts. Pass tokens, bearer tokens, and key-like strings are redacted from any stored body preview.
- SSRF guard. Custom upstreams are resolved and blocked if they point at private, loopback, or cloud-metadata addresses.
- Per-pass containment. A leaked pass is rate-limited, optionally IP-bound, individually revocable, and never exposes the underlying key.
apps/proxy/src/proxy-handler.ts, token.ts, upstream.ts) at a pinned commit rather than relying on any rendered page.Errors
Errors are JSON: { "error": "<code>" }. No internal detail is leaked.
| HTTP | error | Meaning |
|---|---|---|
| 401 | unauthorized | Missing, malformed, or unknown pass |
| 401 | pass_revoked | Pass is revoked or expired |
| 403 | ip_not_allowed | Source IP not in the pass's binding |
| 409 | original_key_required | Pass is pending — real key not yet set |
| 429 | rate_limited | Rate limit hit — honour Retry-After |
| 502 | upstream_unreachable | Upstream error/timeout, or blocked upstream |
Any other status is the upstream provider's own response, passed through unchanged.
Cheat sheet
Proxy https://api.proxykey.org/p/<slug>/<path>
Auth Authorization: Bearer vlt_... (or X-Vault-Pass, or in-path for Telegram)
Panel https://app.proxykey.org
MCP https://mcp.proxykey.org/mcp (Bearer mcp_...)
Rule replace your provider's scheme+host with …/p/<slug> — keep the rest of the path
OpenAI base_url = …/p/openai/v1 key = vlt_openai_...
OpenAI-style openrouter · groq · together · mistral · deepseek (keep each one's own path)
Anthropic …/p/anthropic/v1/messages Authorization: Bearer vlt_anthropic_...
Telegram …/p/telegram-bot/bot<vlt_...>/sendMessage
Any LLM slug openai-compatible + base URL on the secret
Any REST slug generic-rest + base URL + auth type