# RelayKey — Full Agent Reference Self-contained migration manual. An LLM with no other context should be able to migrate a codebase using only this file. Canonical URLs: - Marketing: https://relaykey.ai - Dashboard: https://app.relaykey.ai - Proxy: https://proxy.relaykey.ai - API base: https://app.relaykey.ai/api/v1 - OpenAPI: https://relaykey.ai/api/openapi.yaml ================================================================================ 1. WHAT RELAYKEY IS ================================================================================ RelayKey is an API-key access-control layer. The user creates one "connection" per upstream API (OpenAI, Stripe, Resend, etc.) and gives RelayKey their real master key. RelayKey encrypts the master key at rest (AES-256-GCM). The user never hands out the master key again. In its place, the user issues "delegated credentials" — short-lived, scoped tokens of the form `rk_proxy_xxxx`. Vendors, AI agents, freelancers, and internal services all receive a delegated credential instead of the master key. They send requests to `https://proxy.relaykey.ai/conn_xxx/`. The proxy validates the credential (method, path, IP, expiration), swaps in the master key, and forwards the request upstream. When a delegated credential is revoked, the next request returns 401 from the proxy before any upstream call is made. There is one place to rotate the master key. There is one audit log of every request. ================================================================================ 2. TRUST MODEL (PLAIN ENGLISH) ================================================================================ RelayKey is not zero-knowledge. The proxy decrypts the master key in memory at request time to attach it to the outbound request. This is the same posture as Stripe (your card data passes through their servers), Doppler (your secrets are synced through their service), and Infisical. What RelayKey sees: - The encrypted master key at rest (decryptable only with `RELAYKEY_MASTER_KEY`, which is held in the application environment, not the database). - The plaintext master key in memory on the proxy server, just long enough to attach it to the outbound request. - Request method, path, source IP, response status, response time. (Audit log.) - Query strings — only if the connection has `log_query_strings: true`. - Request and response bodies pass through. They are NOT logged. What RelayKey never returns to anyone: - The master key, after it is created. It is write-only. - The full `rk_proxy_...` token, after it is issued. The dashboard stores only its SHA-256 hash. The raw token is shown once, at creation time. What RelayKey cannot defend against: - Compromise of the user's `rk_mgmt_...` management token. (Treat it like a GitHub PAT.) - Compromise of the proxy host's memory. (Same risk as Stripe's payment servers.) ================================================================================ 3. THE 4-STEP MIGRATION RECIPE ================================================================================ ### Step 1. Find the keys ``` grep -RhoE '^[A-Z_]+_(API_KEY|TOKEN|SECRET)=.+' \ .env .env.local .env.production .env.staging 2>/dev/null ``` Also scan source for `process.env.OPENAI_API_KEY` patterns to be sure each key in `.env` is actually used. ### Step 2. Bulk-import ``` curl -X POST https://app.relaykey.ai/api/v1/import \ -H "Authorization: Bearer rk_mgmt_xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "env": "OPENAI_API_KEY=sk-real-master-key\nANTHROPIC_API_KEY=sk-ant-real\nRESEND_API_KEY=re_real", "default_ttl_seconds": 2592000 }' ``` The user gets `rk_mgmt_...` from `https://app.relaykey.ai/app/settings`. Response (abbreviated): ```json { "imported": [ { "env_var": "OPENAI_API_KEY", "vendor": "openai", "connection_id": "conn_abc123", "credential_id": "dcred_xyz", "proxy_token": "rk_proxy_AbCdEf...", "proxy_base_url": "https://proxy.relaykey.ai/conn_abc123", "snippets": { "env": "OPENAI_API_KEY=rk_proxy_AbCdEf...\nOPENAI_BASE_URL=https://proxy.relaykey.ai/conn_abc123", "node": "new OpenAI({ apiKey: process.env.OPENAI_API_KEY, baseURL: process.env.OPENAI_BASE_URL })", "python": "OpenAI(api_key=os.environ['OPENAI_API_KEY'], base_url=os.environ['OPENAI_BASE_URL'])" } } ], "skipped": [] } ``` ### Step 3. Apply the snippets For each entry in `imported`: - Replace the line in `.env` with `snippets.env`. - Find SDK construction sites in source and replace with `snippets.node` / `snippets.python` / etc. (See section 5 for the canonical pattern for every supported vendor.) ### Step 4. Verify ``` curl -i https://proxy.relaykey.ai/conn_abc123/v1/models \ -H "Authorization: Bearer rk_proxy_AbCdEf..." ``` Expect a `200` from the upstream. If `403` from RelayKey: the credential scope is too narrow. Widen with `PATCH /api/v1/delegated-credentials/{id}`. If `401` from RelayKey: the token is wrong or revoked. If `200` from RelayKey but the response status is upstream's, you are through. Confirm the request appears at `https://app.relaykey.ai/app/audit`. ================================================================================ 4. API REFERENCE ================================================================================ All requests authenticate with `Authorization: Bearer rk_mgmt_...`. -------------------------------------------------------------------------------- POST /api/v1/import -------------------------------------------------------------------------------- Bulk-import a `.env` paste or a JSON list of keys. Detects vendor by env-var name and key prefix, creates connections, issues delegated credentials, returns ready-to-paste SDK snippets. Request: ```json { "env": "OPENAI_API_KEY=sk-...\nRESEND_API_KEY=re_...", "default_ttl_seconds": 2592000, "default_allowed_methods": ["GET", "POST"] } ``` Or, structured form: ```json { "keys": [ { "env_var": "OPENAI_API_KEY", "value": "sk-...", "vendor": "openai" }, { "env_var": "STRIPE_SECRET_KEY", "value": "sk_live_...", "vendor": "stripe" } ] } ``` Response: `{ "imported": [...], "skipped": [...] }`. Each entry contains `connection_id`, `credential_id`, `proxy_token`, `proxy_base_url`, and a `snippets` object keyed by language/format (`env`, `node`, `python`, `curl`). -------------------------------------------------------------------------------- POST /api/v1/connections -------------------------------------------------------------------------------- Create a single connection. Request: ```json { "name": "OpenAI — production", "base_url": "https://api.openai.com", "auth_type": "bearer", "upstream_key": "sk-real-master-key", "log_query_strings": false } ``` For `x-api-key` style: `"auth_type": "header", "auth_header_name": "x-api-key"`. Response: `{ "id": "conn_...", "name": "...", "base_url": "...", ... }`. The upstream key is never returned after creation. -------------------------------------------------------------------------------- POST /api/v1/delegated-credentials -------------------------------------------------------------------------------- Issue a scoped credential bound to a connection. Request: ```json { "connection_id": "conn_abc123", "name": "Vendor X — read only", "allowed_methods": ["GET"], "allowed_paths": ["/v1/models", "/v1/chat/*"], "allowed_ips": ["198.51.100.7/32"], "ttl_seconds": 86400 } ``` Response includes the raw `rk_proxy_...` token ONCE. Store it. -------------------------------------------------------------------------------- PATCH /api/v1/delegated-credentials/{id} -------------------------------------------------------------------------------- Widen (or narrow) scope on an existing credential. Common when the verification curl returns 403. Request: ```json { "allowed_methods": ["GET", "POST", "DELETE"], "allowed_paths": ["/v1/*"] } ``` The `rk_proxy_...` token does not change. Existing integrations keep working. -------------------------------------------------------------------------------- POST /api/v1/delegated-credentials/{id}/revoke -------------------------------------------------------------------------------- Revoke. Subsequent proxy requests with that token return 401. -------------------------------------------------------------------------------- GET /api/v1/audit -------------------------------------------------------------------------------- Filters: `connection_id`, `credential_id`, `since`, `until`, `limit`. Returns method, path, status, source IP, latency. Bodies are not stored. ================================================================================ 5. VENDOR CATALOG ================================================================================ Each vendor: env vars, base URL, auth shape, before/after snippet. -------------------------------------------------------------------------------- OpenAI -------------------------------------------------------------------------------- Env vars: `OPENAI_API_KEY`, optional `OPENAI_BASE_URL` Base URL: `https://api.openai.com` Auth: `Authorization: Bearer ...` Before (Node): ``` import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); ``` After: ``` import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, baseURL: process.env.OPENAI_BASE_URL, }); ``` Before (Python): ``` from openai import OpenAI client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) ``` After: ``` from openai import OpenAI client = OpenAI( api_key=os.environ["OPENAI_API_KEY"], base_url=os.environ["OPENAI_BASE_URL"], ) ``` curl: ``` curl https://proxy.relaykey.ai/conn_xxx/v1/chat/completions \ -H "Authorization: Bearer rk_proxy_..." \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}' ``` -------------------------------------------------------------------------------- Anthropic -------------------------------------------------------------------------------- Env vars: `ANTHROPIC_API_KEY`, optional `ANTHROPIC_BASE_URL` Base URL: `https://api.anthropic.com` Auth: `x-api-key: ...` Before (Node): ``` import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); ``` After: ``` import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY, baseURL: process.env.ANTHROPIC_BASE_URL, }); ``` Python: ``` from anthropic import Anthropic client = Anthropic( api_key=os.environ["ANTHROPIC_API_KEY"], base_url=os.environ["ANTHROPIC_BASE_URL"], ) ``` -------------------------------------------------------------------------------- ElevenLabs -------------------------------------------------------------------------------- Env vars: `ELEVENLABS_API_KEY`, `ELEVENLABS_BASE_URL` Base URL: `https://api.elevenlabs.io` Auth: `xi-api-key: ...` (custom header — set `auth_type: "header"`, `auth_header_name: "xi-api-key"` on the connection.) Node: ``` import { ElevenLabsClient } from "elevenlabs"; const client = new ElevenLabsClient({ apiKey: process.env.ELEVENLABS_API_KEY, environment: process.env.ELEVENLABS_BASE_URL, }); ``` Python: ``` from elevenlabs.client import ElevenLabs client = ElevenLabs( api_key=os.environ["ELEVENLABS_API_KEY"], base_url=os.environ["ELEVENLABS_BASE_URL"], ) ``` curl: ``` curl https://proxy.relaykey.ai/conn_xxx/v1/voices \ -H "xi-api-key: rk_proxy_..." ``` -------------------------------------------------------------------------------- Resend -------------------------------------------------------------------------------- Env vars: `RESEND_API_KEY`, `RESEND_BASE_URL` Base URL: `https://api.resend.com` Auth: `Authorization: Bearer ...` Node: ``` import { Resend } from "resend"; const resend = new Resend(process.env.RESEND_API_KEY, { baseUrl: process.env.RESEND_BASE_URL, }); ``` curl: ``` curl -X POST https://proxy.relaykey.ai/conn_xxx/emails \ -H "Authorization: Bearer rk_proxy_..." \ -H "Content-Type: application/json" \ -d '{"from":"a@b.com","to":"c@d.com","subject":"hi","text":"hi"}' ``` -------------------------------------------------------------------------------- Cloudflare -------------------------------------------------------------------------------- Env vars: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_BASE_URL` Base URL: `https://api.cloudflare.com` Auth: `Authorization: Bearer ...` curl: ``` curl https://proxy.relaykey.ai/conn_xxx/client/v4/zones \ -H "Authorization: Bearer rk_proxy_..." ``` -------------------------------------------------------------------------------- Fly.io -------------------------------------------------------------------------------- Env vars: `FLY_API_TOKEN`, `FLY_BASE_URL` Base URL: `https://api.machines.dev` Auth: `Authorization: Bearer ...` curl: ``` curl https://proxy.relaykey.ai/conn_xxx/v1/apps \ -H "Authorization: Bearer rk_proxy_..." ``` -------------------------------------------------------------------------------- HiBob -------------------------------------------------------------------------------- Env vars: `HIBOB_API_TOKEN`, `HIBOB_BASE_URL` Base URL: `https://api.hibob.com` Auth: Basic, but commonly set as `Authorization` header. curl: ``` curl https://proxy.relaykey.ai/conn_xxx/v1/people \ -H "Authorization: Basic rk_proxy_..." ``` -------------------------------------------------------------------------------- Absorb LMS -------------------------------------------------------------------------------- Env vars: `ABSORB_API_KEY`, `ABSORB_BASE_URL` Base URL: `https://api.absorblms.com` Auth: `Authorization: Bearer ...` after `/authenticate` exchange. curl: ``` curl https://proxy.relaykey.ai/conn_xxx/users \ -H "Authorization: Bearer rk_proxy_..." ``` -------------------------------------------------------------------------------- MedFlyt -------------------------------------------------------------------------------- Env vars: `MEDFLYT_API_KEY`, `MEDFLYT_BASE_URL` Base URL: `https://api.medflyt.com` Auth: `Authorization: Bearer ...` curl: ``` curl https://proxy.relaykey.ai/conn_xxx/api/v1/visits \ -H "Authorization: Bearer rk_proxy_..." ``` -------------------------------------------------------------------------------- TalkDesk -------------------------------------------------------------------------------- Env vars: `TALKDESK_API_KEY`, `TALKDESK_BASE_URL` Base URL: `https://api.talkdeskapp.com` Auth: `Authorization: Bearer ...` curl: ``` curl https://proxy.relaykey.ai/conn_xxx/contacts \ -H "Authorization: Bearer rk_proxy_..." ``` -------------------------------------------------------------------------------- Stripe -------------------------------------------------------------------------------- Env vars: `STRIPE_SECRET_KEY`, `STRIPE_BASE_URL` Base URL: `https://api.stripe.com` Auth: Basic auth with the secret key as username; the Stripe SDK does this automatically. With RelayKey, pass `host`/`protocol` overrides. Node: ``` import Stripe from "stripe"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { host: "proxy.relaykey.ai", protocol: "https", port: 443, // SDK will append /v1/...; ensure base_url on the connection // is https://proxy.relaykey.ai/conn_xxx and the connection's base_url // strip-prefix matches. }); ``` Python: ``` import stripe, os stripe.api_key = os.environ["STRIPE_SECRET_KEY"] stripe.api_base = os.environ["STRIPE_BASE_URL"] ``` Note: Stripe webhooks are NOT proxied. Webhook signing secrets are different from API keys and stay on the user's server. -------------------------------------------------------------------------------- GitHub -------------------------------------------------------------------------------- Env vars: `GITHUB_TOKEN`, `GITHUB_BASE_URL` Base URL: `https://api.github.com` Auth: `Authorization: Bearer ...` (or `token ...` for legacy). Octokit (Node): ``` import { Octokit } from "octokit"; const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN, baseUrl: process.env.GITHUB_BASE_URL, }); ``` PyGithub: ``` from github import Github g = Github( base_url=os.environ["GITHUB_BASE_URL"], login_or_token=os.environ["GITHUB_TOKEN"], ) ``` curl: ``` curl https://proxy.relaykey.ai/conn_xxx/user \ -H "Authorization: Bearer rk_proxy_..." ``` -------------------------------------------------------------------------------- Generic axios / fetch / requests / urllib -------------------------------------------------------------------------------- axios: ``` const client = axios.create({ baseURL: process.env.VENDOR_BASE_URL, headers: { Authorization: `Bearer ${process.env.VENDOR_API_KEY}` }, }); ``` fetch: ``` fetch(`${process.env.VENDOR_BASE_URL}/some/path`, { headers: { Authorization: `Bearer ${process.env.VENDOR_API_KEY}` }, }); ``` requests (Python): ``` requests.get( f"{os.environ['VENDOR_BASE_URL']}/some/path", headers={"Authorization": f"Bearer {os.environ['VENDOR_API_KEY']}"}, ) ``` ================================================================================ 6. COMMON PITFALLS ================================================================================ - Webhooks are NOT proxied. Webhook signing secrets are vendor-to-customer; RelayKey is customer-to-vendor. Leave webhook secrets alone. - OAuth refresh flows are NOT proxied. RelayKey supports bearer tokens and custom-header API keys. If the vendor SDK transparently refreshes, that flow must reach the upstream directly. - Some SDKs hardcode hostnames internally (Twilio's old SDKs, AWS SDK v2). Check the SDK's docs for a `baseURL`/`endpoint`/`host` option. If none exists, fall back to a thin axios/requests wrapper. - Streaming responses larger than 10MB buffer in the proxy. Plan accordingly. - Query strings are not logged in audit by default. Set `log_query_strings: true` on the connection if the user needs them. - Do NOT put `rk_proxy_...` tokens in client-side bundles (browser, mobile). They are server-side credentials. - Do NOT commit `rk_mgmt_...` to git. - The `rk_proxy_...` token is shown once at creation. Store it in `.env` immediately. Lost? Revoke and re-issue. ================================================================================ 7. VERIFICATION RECIPE ================================================================================ ``` # 1. Hit a known-safe upstream endpoint through the proxy. curl -i https://proxy.relaykey.ai/conn_abc123/v1/models \ -H "Authorization: Bearer rk_proxy_..." # 2. Confirm a 2xx response with upstream's body. # 3. List recent audit events for the credential. curl https://app.relaykey.ai/api/v1/audit?credential_id=dcred_xyz \ -H "Authorization: Bearer rk_mgmt_..." # 4. Optional: revoke and re-curl. Expect 401 from RelayKey. curl -X POST \ https://app.relaykey.ai/api/v1/delegated-credentials/dcred_xyz/revoke \ -H "Authorization: Bearer rk_mgmt_..." ``` If steps 1-3 succeed, the migration is complete.