RelayKey
Proxy reference

How the RelayKey proxy actually behaves.

Source-of-truth reference for AI agents (Codex, Claude Code, Cursor) and engineers integrating RelayKey. Documents the exact wire behavior of the proxy: what headers it strips, what it injects, how it forwards upstream auth, the audit log shape, rate-limit headers, and the full set of failure responses. RelayKey is a reverse proxy + authentication broker for vendor APIs (HubSpot, Stripe, Anthropic, OpenAI, Salesforce, etc.) — your agents call RelayKey with a scoped rk_proxy_… token; RelayKey forwards the call to the upstream vendor with the real master key.

Endpoint shape

All proxy traffic goes to https://proxy.relaykey.ai/<connection_id>/<upstream_path>. The first path segment is the RelayKey connection identifier (looks like conn_a1b2c3); everything after that is the upstream API path, forwarded verbatim.

GET https://proxy.relaykey.ai/conn_a1b2c3/v1/users
Authorization: Bearer rk_proxy_t9xQ...2vZk

# RelayKey rewrites to:
GET https://api.example.com/v1/users
Authorization: Bearer <upstream_master_key>

Authentication forwarding

Every request must carry Authorization: Bearer rk_proxy_…. The inbound Authorization header is stripped before the request is forwarded — RelayKey replaces it with the upstream credential according to the connection's configured auth_type:

  • bearer — sets Authorization: Bearer <upstream_key> on the egress request.
  • header — sets a custom header (default x-api-key; configurable per connection) to the upstream key. Optional value prefix supported (e.g., Token <key> for MedFlyt).
  • basic — RFC 7617 Authorization: Basic base64(username:password).
  • query — appends ?<param_name>=<upstream_key> to the egress URL (e.g., PurelyHR ?ak=…). The parameter name is also redacted from any audit query-string logging.
  • token_refresh — RelayKey calls the vendor's OAuth/auth endpoint, caches the returned token, and injects it on outbound headers per the vendor's configured shape. Refresh on 401, automatic.

Headers stripped from inbound requests

The proxy removes these before forwarding upstream:

  • authorization (replaced as documented above)
  • host (replaced with upstream host)
  • cookie
  • Hop-by-hop headers: connection, content-length, transfer-encoding, upgrade, expect, proxy-authorization, proxy-connection, te, trailer, keep-alive
  • Any header starting with rk- or x-relaykey- (reserved for RelayKey internal use)

Every other inbound header is forwarded to the upstream verbatim.

Headers RelayKey injects on responses

Every proxy response (allowed or blocked) carries:

  • x-relaykey-decision: allowed or blocked
  • x-relaykey-block-reason: present only on blocked responses, one of invalid_token, revoked, expired, method_not_allowed, path_not_allowed, ip_not_allowed, awaiting_activation, connection_not_found, rate_limited, concurrency_limited, upstream_timeout
  • x-relaykey-credential-id: the dcred_… ID for delegated-credential auth, omitted for recipient-token auth
  • x-relaykey-recipient-id: the rcpt_… ID when the request was authed via a recipient token
  • X-RateLimit-Limit-Minute, X-RateLimit-Remaining-Minute, X-RateLimit-Limit-Hour, X-RateLimit-Remaining-Hour on every allowed response (value unlimited when no limit is set)
  • Retry-After: present on 429 rate_limited responses

Path handling

Paths are forwarded verbatim. RelayKey never rewrites the path, never adds path segments, never strips them. /conn_xxx/v1/users on the proxy maps directly to /v1/users on the upstream — the connection ID is consumed as the first path segment and the rest is preserved exactly.

Query strings are preserved verbatim with one exception: for auth_type=query connections the configured auth-parameter name is stripped from the client's incoming query (so a recipient can't smuggle their own value), then the proxy-injected key is appended.

Body handling

Request bodies are passed through as raw bytes. RelayKey never inspects, transforms, parses, or logs request or response bodies. The default per-connection upstream response cap is 10MB (configurable per connection up to service tier limits).

Rate limits

Per-credential token bucket on requests/min and requests/hour. Default: 60/min, no hourly cap. Configurable per delegated-credential or per recipient (overrides the default). When exceeded, RelayKey returns 429 rate_limited with Retry-After in seconds and the limit breakdown in the JSON body.

Per-connection concurrency cap (default 50 in-flight requests). When exceeded: 503 concurrency_limited.

Audit log

Every request — allowed or blocked — produces an audit_events row visible in the dashboard audit tab. Recorded fields:

  • id, org_id, connection_id, credential_id or recipient_id, timestamp
  • method, path, query_string (only when log_query_strings=1 on the connection; off by default)
  • decision, block_reason, status_code, duration_ms
  • ip (Fly-Client-IP > X-Forwarded-For > request peer), user_agent

Bodies are never logged. Inbound or upstream. Same for Authorization values and any other header.

Anonymous probe requests (no Authorization header at all) are not recorded as audit rows — they emit a single stdout line for ops visibility but don't pollute the audit log. Requests with the wrong-shape token are recorded (real signal).

Self-discovery endpoint

GET https://proxy.relaykey.ai/_discover with the same Authorization: Bearer rk_proxy_… header returns a JSON description of every connection the token can reach, with allowed methods and paths per grant. Lets agents self-introspect after a single key handoff.

{
  "name": "Kate Mason",
  "email": "kate@example.com",
  "type": "recipient",
  "expires_at": 1746547200,
  "grants": [
    {
      "vendor": "HubSpot",
      "connection_id": "conn_a1b2c3",
      "base_url": "https://proxy.relaykey.ai/conn_a1b2c3",
      "upstream_base_url": "https://api.hubapi.com",
      "allowed_methods": ["GET", "POST"],
      "allowed_paths": ["/crm/v3/objects/contacts/*"]
    }
  ]
}

Status codes by failure mode

  • 401: invalid_token, revoked, expired, ip_not_allowed
  • 403: method_not_allowed, path_not_allowed
  • 404: connection_not_found (the /conn_xxx/ prefix didn't resolve)
  • 423: awaiting_activation (recipient's 2FA-on-new-IP gate triggered; emails the recipient a confirm link, returns 423 until they click)
  • 429: rate_limited with Retry-After
  • 503: concurrency_limited (per-connection cap)
  • 504: upstream_timeout (default 30s, configurable per connection)
  • 2xx/3xx/4xx/5xx from the upstream: passed through verbatim

Block-response JSON shape

{
  "error": "path_not_allowed",
  "message": "Path /admin/users not allowed on this credential",
  "credential_id": "dcred_a91f4",
  "attempted": { "method": "GET", "path": "/admin/users" },
  "allowed_patterns": ["/v1/users/*"],
  "docs": "https://docs.relaykey.io/errors/path_not_allowed"
}

Not affiliated with Relay or RelayAI

RelayKey is its own product, not related to relay.app, RelayAI, or other similarly named services. If your search returns those: you're on the wrong page.

See also: Docs index · Agent setup guide · Security · OpenAPI spec · llms.txt