Authentication
Per-tenant API keys via /v1/api-keys. DEV_API_KEY remains available as a fallback for local development.
All /v1/* endpoints require a Bearer token in the Authorization header:
curl http://localhost:3001/v1/skills \
-H "Authorization: Bearer rle-<your-key>"Missing or incorrect → 401 unauthorized.
Per-tenant API keys (recommended)
Relay-E issues API keys per tenant. Each key is scoped to a single tenant — a key can only access that tenant's connectors, skills, sessions, and usage data.
Create a key
POST /v1/api-keys
Authorization: Bearer <existing-key-or-DEV_API_KEY>
Content-Type: application/json
{ "name": "production" }Response (key shown once — store it securely):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "production",
"prefix": "rle-a1b2c3",
"raw": "rle-a1b2c3d4e5f6...",
"createdAt": "2026-04-26T00:00:00.000Z"
}The raw field is the key to use in Authorization: Bearer. It is never returned again after this response.
List keys
GET /v1/api-keys
Authorization: Bearer <key>Returns active (non-revoked) keys for the calling tenant — no hashes, no raw values:
{
"data": [
{
"id": "550e8400-...",
"name": "production",
"prefix": "rle-a1b2c3",
"lastUsedAt": "2026-04-26T10:00:00.000Z",
"createdAt": "2026-04-26T00:00:00.000Z"
}
]
}Revoke a key
DELETE /v1/api-keys/{id}
Authorization: Bearer <key>Soft-deletes the key by setting revoked_at. Subsequent requests using the revoked key receive 401.
DEV_API_KEY (local development only)
For local development, set DEV_API_KEY in .env:
DEV_API_KEY=sk_dev_local_change_meWhen a request arrives with this key and no matching DB row is found, Relay-E falls back to a built-in dev-tenant. This avoids needing a database round-trip during initial setup.
Do not use DEV_API_KEY in production. It bypasses the per-tenant key lookup and always resolves to the same internal tenant. Treat it as a master key.
In production
For multi-tenant SaaS, each of your customers gets their own API key via POST /v1/api-keys. Your application:
- Creates a tenant (or derives one from your own user model).
- Calls
POST /v1/api-keyson behalf of that tenant to issue a key. - Stores the key securely in your own secrets manager or sends it to the customer.
- The customer uses the key directly in
Authorization: Bearer.
For setups where end users should never see a Relay-E key, proxy through your own backend:
export async function POST(req: Request, { params }: { params: { path: string[] } }) {
const user = await authenticateRequestSomehow(req);
if (!user) return new Response("unauthorized", { status: 401 });
const url = `${process.env.RELAY_E_URL}/v1/${params.path.join("/")}`;
return fetch(url, {
method: "POST",
headers: {
"authorization": `Bearer ${process.env.RELAY_E_KEY}`,
"content-type": "application/json",
},
body: req.body,
});
}Your customers never see the Relay-E key — your backend holds it and proxies on their behalf.
Common errors
{
"error": {
"type": "authentication_error",
"code": "unauthorized",
"message": "Missing or invalid API key",
"request_id": "req_..."
}
}The same response is returned whether the key is missing or wrong — don't leak whether a key exists. Use request_id for debugging.
Roadmap
| Coming | Why |
|---|---|
| Scoped tokens (skills, sessions, expiry) | Customer-issuable, narrowly-scoped |
| OAuth (Authorization Code + PKCE) | Proper SaaS auth |
| mTLS | High-compliance customers |
| JWT support | Easy integration with Clerk / WorkOS / Auth0 / Cognito |

