Documentation

The gateway forwards your traffic; the control plane API on this page configures it. A change made through the API reaches the gateway within seconds.

set this to rewrite the samples

Overview

Every request to your subdomain runs through the same pipeline: match a route by path and priority, apply its auth mode, spend a rate-limit token, check the response cache, then forward to a healthy origin in the route’s pool. The outcome (status, latency, cache hit, limit hit) is written to the request log, which is what the Traffic panel reads.

The control plane API lives under /api/v1. Every route requires a verified Clerk JWT. The tenant is resolved from the token; you never pass a tenant id. On your first authenticated call, a tenant is provisioned for you automatically.

# base URL
https://<your-control-plane-host>/api/v1

# every request
Authorization: Bearer <clerk session jwt>
If your Clerk instance needs a JWT template for the backend audience, set NEXT_PUBLIC_CLERK_JWT_TEMPLATE and the console mints the token with it.

Authentication

Two ways in. People use a Clerk session; the browser attaches Authorization: Bearer <jwt> automatically. Machines use an API key in the X-API-Key header, issued from the Keys panel with the scopes it needs.

# session (what the console sends)
curl https://<host>/api/v1/routes \
  -H "Authorization: Bearer $CLERK_JWT"

# machine caller
curl https://<host>/api/v1/routes \
  -H "X-API-Key: ve_live_..."

Origins

An origin is a backend the gateway can forward to. Health checks run on the interval you set; a failing origin is taken out of rotation until it passes again.

curl -X POST https://<host>/api/v1/origins \
  -H "Authorization: Bearer $CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "orders-api",
    "url": "https://orders.internal.example.com",
    "health_check_path": "/health",
    "health_check_interval": 30,
    "timeout_seconds": 5,
    "max_retries": 3,
    "weight": 1
  }'

Routes

A route binds a path pattern to an origin and carries its policy: methods, priority, auth mode, rate limit, cache. Higher priority wins when patterns overlap.

The gateway tries your active routes from the highest priority down and takes the first one whose path and method both match. Edit the table or the request to check a setup before you create it:

Try:
Path does not match.
Matches. This route handles the request.
Path does not match.
Also matches, but priority 10 is tried first.
GET /api/orders/1042 goes to /api/orders/*.

Build the create call. The summary under the form says what the gateway will do with it.

Methods (none ticked means all)

What this route does

  • GET, POST on /api/orders/*, where * matches anything including slashes, goes to the route's origin pool, picked at random by origin weight.
  • Callers need a valid Clerk JWT.
  • Rate limit per each caller (API key or signed-in user, else client IP): up to 20 requests at once, refilling at 50 per second. Past that the gateway answers 429 with a Retry-After header.
  • Nothing is cached.
curl -X POST https://<host>/api/v1/routes \
  -H "Authorization: Bearer $CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "origin_id": "<origin uuid>",
    "name": "orders",
    "path_pattern": "/api/orders/*",
    "methods": [
      "GET",
      "POST"
    ],
    "priority": 10,
    "auth_mode": "jwt_required",
    "load_balancing": "weighted",
    "is_active": true,
    "rate_limit_enabled": true,
    "rate_limit_requests_per_second": 50,
    "rate_limit_burst": 20,
    "rate_limit_key_strategy": "tenant_user",
    "cache_enabled": false
  }'

Origin pools

Beyond its primary origin, a route can load-balance across a pool. Members are health-checked like any origin. The route’s load_balancing picks how a request is assigned: weighted (default, random by origin weight), round_robin,least_conn (fewest in-flight requests), or ip_hash (a client IP sticks to one origin).

API keys

Keys authenticate machine callers. The secret is returned once, on creation. Scopes are read, write, admin; expiry is optional.

curl -X POST https://<host>/api/v1/api-keys \
  -H "Authorization: Bearer $CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{ "name": "ci-deploy", "scopes": ["read","write"], "expires_at": "2026-12-31T23:59:59Z" }'
# -> { ..., "key": "ve_live_xxx" }   (shown once)

Analytics

A rollup of the request log for a time window. Buckets are hourly for 1h and 24h, daily beyond that.

curl "https://<host>/api/v1/analytics?window=24h" \
  -H "Authorization: Bearer $CLERK_JWT"

{
  "window": "24h",
  "generated_at": "2026-08-29T12:00:00Z",
  "totals": {
    "total_requests": 48210,
    "error_rate": 0.012,
    "cache_hit_rate": 0.41,
    "rate_limited_count": 88,
    "avg_latency_ms": 37.4,
    "p95_latency_ms": 121.0
  },
  "series": [ { "ts": "...", "count": 2010, "avg_latency_ms": 35.1, "error_count": 12 } ],
  "status_breakdown": { "200": 46110, "404": 900, "500": 210 },
  "top_routes": [ { "path": "/api/orders/*", "count": 12005, "avg_latency_ms": 41.2, "error_count": 30 } ]
}

Auth modes

Set per route, in auth_mode. Pick one to see what a caller has to send:

A valid Clerk JWT must be present.

curl https://<host>/api/v1/routes \
  -H "Authorization: Bearer $JWT"   # required

missing it: 401 Unauthorized

Errors

What the gateway itself answers with, as opposed to responses your origin sends back. Every one of these is also written to the request log, so it shows up in Traffic.

401UnauthorizedThe route needs a JWT or API key the request did not carry, or carried an invalid one.
403Tenant suspendedThe tenant behind this subdomain is suspended.
404Unknown tenantNo tenant owns the subdomain in the Host (or X-Tenant-Subdomain) header.
404Route not foundNo active route matches the path and method.
429Rate limit exceededThe route’s bucket is empty for this caller. Retry-After says how many seconds to wait.
502Bad gatewayThe origin could not be reached. GET and HEAD are retried first, on a different origin when the pool has one.
503Service unavailableThe route has no origin to send to, or the gateway could not load your config.

A response served from the cache carries X-Cache: HIT.