Skip to content

API Gateway

An api_gateway project turns PromptGate into a generic HTTP proxy in front of any upstream API. Clients authenticate to PromptGate with a Bearer token; PromptGate forwards to the configured upstream while enforcing method, header, rate-limit, and SSRF policies. Optional OAuth Service Connections inject the right upstream Authorization header automatically.

ANY /api/{project_uuid}/proxy/{endpoint_slug}/{any?}

Any HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS) — the endpoint’s allowed_methods decides which actually pass through. The trailing path is appended to the upstream URL.

Bearer-token gated, scope proxy.

FieldMeaning
name / slugUI label / URL segment.
upstream_urlWhere the proxy forwards to (e.g. https://api.openweathermap.org).
allowed_methodsList of permitted HTTP verbs.
forward_headersAllowlist of client headers to forward. Empty = forward all.
blocked_headersAlways blocked (in addition to defaults: Host, Authorization, Cookie, Content-Length).
inject_headersServer-side headers added to every upstream request.
auth_modeCurrently token (Bearer required).
oauth_connection_idOptional FK — when set, Authorization: Bearer <upstream-token> is injected and refreshed automatically.
timeout_secondsUpstream timeout (1–300).
strip_path_prefixIf true (default), /proxy/{slug}/ is stripped before appending.
preserve_queryIf true (default), the client’s query string is forwarded.
rate_limit_per_minute / _hour429 + Retry-After when exceeded.

In your api_gateway project: sidebar → Endpoints+ New endpoint.

The form covers all fields above, with sensible defaults (timeout 30s, strip prefix on, preserve query on).

If you registered an endpoint with slug weather pointing at https://api.openweathermap.org, then:

Terminal window
# This client request:
GET /api/$UUID/proxy/weather/data/2.5/weather?q=Berlin
# Becomes this upstream request:
GET https://api.openweathermap.org/data/2.5/weather?q=Berlin
Terminal window
curl $URL/api/$UUID/proxy/weather/data/2.5/weather?q=Berlin \
-H "Authorization: Bearer pg_live_..."
import os, requests
r = requests.get(
f"{os.environ['PG_URL']}/api/{os.environ['PG_UUID']}/proxy/weather/data/2.5/weather",
headers={"Authorization": f"Bearer {os.environ['PG_TOKEN']}"},
params={"q": "Berlin"},
)
print(r.json())
const url = `${process.env.PG_URL}/api/${process.env.PG_UUID}/proxy/weather/data/2.5/weather?q=Berlin`;
const r = await fetch(url, {
headers: { 'Authorization': `Bearer ${process.env.PG_TOKEN}` },
});
console.log(await r.json());

The default behaviour is permissive but safe:

  • Always blocked, never forwarded: Host, Authorization, Cookie, Content-Length.
  • forward_headers empty: every other client header is forwarded.
  • forward_headers populated: only the listed headers are forwarded.
  • blocked_headers: extra headers blocked on top of the defaults.
  • inject_headers: added to every upstream request.

Why is Authorization always blocked? Because the client’s Authorization: Bearer pg_live_… is for PromptGate, not the upstream. Upstream credentials come from inject_headers or an OAuth connection — never from the client.

Two strategies for letting the proxy authenticate to upstream:

Hard-code the secret on the server side. Example for an internal API:

X-API-Key: super-secret-key
X-Source: promptgate

Stored unencrypted in DB (it’s just a header value). Use this for low-risk static credentials.

For real upstream OAuth providers (Google, GitHub, Slack, …), use a connection. The proxy injects Authorization: Bearer <access_token> and auto-refreshes when the token’s expires_at is past (or within 60s). Tokens are encrypted at rest.

See OAuth Connections.

Every upstream_url is validated:

  • At endpoint create / update — friendly error in the form.
  • At proxy time — defends against DNS rebinding (a hostname that resolved publicly when saved can flip private later).

Blocked: loopback, RFC1918, link-local, IPv6 unique-local, cloud metadata (169.254.169.254). Override with SSRF_ALLOWED_HOSTS env var.

See SSRF Protection for the full ranges and the override grammar.

rate_limit_per_minute and rate_limit_per_hour are independent. Either tripping fires a 429 with Retry-After. Empty fields = unlimited. See Rate Limits.

Every proxy request lands in gateway_logs with:

  • provider_key = "http"
  • provider_model = "<METHOD> <STATUS>" (e.g. GET 200, POST 503)
  • latency_ms — full round-trip including upstream call
  • statusok for 2xx/3xx, error for 4xx/5xx
  • error_message — populated on SSRF block, OAuth failure, or upstream unreachable

So Live Logs and Metrics work the same way they do for AI traffic.

  • Public API behind a token — register the upstream, set forward_headers=[], no OAuth. Clients use a proxy-scoped PromptGate token instead of figuring out the upstream’s auth.
  • Internal microservice gateway — register with inject_headers: { "X-Internal-Auth": "..." }. Clients never see the internal credential.
  • Third-party SaaS via OAuth — register the upstream + an OAuth Service Connection bound to it. Run the auth flow once; the proxy keeps the token fresh.
  • Read-only proxy — set allowed_methods: ["GET", "HEAD"]. Mutations get a 405.
SituationResponse
Unknown endpoint slug or inactive404
Method not in allowed_methods405
Upstream URL fails SSRF check422
OAuth refresh fails502
Upstream times out / unreachable502
Rate limit exceeded429 + Retry-After
Wrong scope403
Wrong project type for /proxy route404

Next: Sessions — server-side conversation state for AI Gateway.


© Akyros Labs LLC. All rights reserved.