Skip to main content

REST API

The gateway exposes a JSON control-plane API under /api/ on its single HTTP listener (port 8080 in the default Compose setup). The dashboard is a pure frontend over this API — anything the dashboard does, you can do with curl.

warning

The API is pre-1.0 and subject to change. Routes documented here are the ones registered in the gateway source today (internal/api).

Authentication

All routes except /api/auth/* require a valid gig_session cookie, issued by the gateway after a successful OIDC login. There is no API-key authentication for the control plane today — see Authentication for the full login flow.

Two additional layers apply on top of the session:

  • Admin-only routes (marked below) require the real user's role to be admin. Roles come from the IdP; there is no local role editing.
  • Impersonation is read-only. While an admin is impersonating a user ("view-as"), every non-GET/HEAD request returns 403, with one carve-out: DELETE /api/admin/impersonate to stop impersonating.

The control-plane API is only mounted when OIDC is configured; without it the gateway disables /api/ entirely (a descriptive 404 tells you which env vars to set) while the MCP data plane keeps working.

note

Profile MCP endpoints (/mcp/p/<slug>) use per-profile bearer tokens, not sessions. That is the data plane, not this API — see Profiles.

Conventions

  • Responses are application/json with Cache-Control: no-store.
  • Request bodies are strict JSON: unknown fields are rejected, and bodies are capped at 64 KiB (413 beyond that).
  • Errors use a uniform envelope, including bare 404/405s from the router:
{
"error": {
"code": "not_found",
"message": "profile not found"
}
}
CodeTypical status
unauthenticated401
forbidden403
impersonating403 (mutation attempted during view-as)
not_found404
method_not_allowed405
invalid400 / 413
conflict409
internal500
install_failed / uninstall_failed502

Mutations write synchronous rows to the audit log.

Auth routes

Unauthenticated — these are the authentication. Mounted only when OIDC is configured.

MethodPathPurpose
GET/api/auth/loginStart the OIDC flow (sets a signed, 10-minute flow cookie carrying state/nonce/PKCE).
GET/api/auth/callbackOIDC redirect target; on success sets the gig_session cookie.
POST/api/auth/logoutClear the session (local logout).

Session

MethodPathAuthPurpose
GET/api/mesessionThe effective user; while impersonating, also the real (admin) identity.
{
"user": { "id": 2, "email": "user@example.com", "display_name": "User", "role": "user" },
"impersonating": true,
"real_user": { "id": 1, "email": "admin@example.com", "display_name": "Admin", "role": "admin" }
}

real_user is omitted when not impersonating.

Profiles

A profile is one MCP endpoint (/mcp/p/<slug>) with its own bearer token and server set. Users see their own profiles; admins see all. Requesting someone else's profile by ID returns 404 (not 403) so existence never leaks.

MethodPathAuthPurpose
GET/api/profilessessionList profiles (own; all if admin).
POST/api/profilessessionCreate a profile. Returns the bearer token plaintext — exactly once.
GET/api/profiles/{id}owner or adminProfile detail incl. endpoint and servers. Never the token.
PATCH/api/profiles/{id}owner or adminRename ({"name": "..."}).
DELETE/api/profiles/{id}owner or adminDelete; cascades server assignments and tears down the profile's MCP runtime. 204.
PUT/api/profiles/{id}/serversowner or adminReplace the full server list ({"servers": ["echo"]}). Every name must be an installed server. Invalidates the runtime.
POST/api/profiles/{id}/tokenowner or adminRotate the bearer token; returns the new plaintext once. Does not restart the runtime.

Create body:

{ "name": "My profile", "slug": "my-profile" }
  • name: 1–128 characters.
  • slug: must match ^[a-z0-9][a-z0-9-]{1,62}$; 409 conflict if taken.

Profile object:

{
"id": 1,
"slug": "my-profile",
"name": "My profile",
"user_id": 2,
"endpoint": "/mcp/p/my-profile",
"servers": ["echo"],
"token": "…only present on create and rotate…"
}

Credentials

Credentials are write-only: the secret is envelope-encrypted into the vault on PUT and never returned. List responses contain metadata only. Credentials are scoped per user (tenant).

MethodPathAuthPurpose
GET/api/credentialssessionList your credentials — metadata only, never the secret.
PUT/api/credentials/{server}sessionEncrypt and upsert a secret for a server. 204.
DELETE/api/credentials/{server}sessionDelete (idempotent). 204.

{server} must match ^[a-z0-9][a-z0-9_-]{0,63}$. A credential may be created before the server is installed.

PUT body:

{
"secret": "ghp_…",
"inject_header": "Authorization",
"inject_format": "Bearer {token}",
"placeholder": "PLACEHOLDER_TOKEN",
"allowed_hosts": ["api.github.com", "*.example.com"]
}
FieldRequiredConstraints
secretyesNon-empty; up to 64 KiB (service-account JSON fits).
inject_headernoHTTP header name chars, ≤ 64 chars.
inject_formatno≤ 256 chars; must contain {token}.
placeholderno8–256 chars.
allowed_hostsno≤ 64 entries; each ^(\*\.)?[a-z0-9.-]+$, ≤ 253 chars.
note

inject_header, inject_format, placeholder, and allowed_hosts are transitional: once registry manifests become the source of injection config, the body shrinks to {secret} and these fields go away. See Manifests.

Servers

Server install/uninstall pulls digest-pinned images from the signed registry index — see Registry overview.

MethodPathAuthPurpose
GET/api/serverssessionList installed servers ([{"id": 1, "name": "echo"}]).
POST/api/servers/installadminInstall by registry ref: {"ref": "name"}. 201 with the server, 502 install_failed on error.
DELETE/api/servers/{name}adminUninstall; removes the server from all profiles and invalidates their runtimes. Idempotent — re-run to retry cleanup. 204.

If the gateway is running without a registry installer configured, install/uninstall return 501.

Users

MethodPathAuthPurpose
GET/api/usersadminRead-only user list. Roles come from the IdP; there is no local user editing.

Impersonation

Config-only "view-as": the admin sees the target's profiles, servers, and audit view, but cannot mutate anything or touch secrets. Time-boxed (max 60 minutes), and loudly audited — the event is attributed to the target so it appears in their own audit view.

MethodPathAuthPurpose
POST/api/admin/impersonateadminStart: {"user_id": 2, "ttl_minutes": 30}. TTL is clamped to 60 minutes. You cannot impersonate yourself. 204.
DELETE/api/admin/impersonateadminStop. The only mutation allowed while impersonating — switching targets requires stopping first. 204.

Audit

MethodPathAuthPurpose
GET/api/auditsessionAudit events, newest first, keyset-paginated.

Query parameters:

ParamDescription
beforeReturn events with ID lower than this (from the previous page's next_before).
limitPage size, 1–200; default 50.
user_idFilter by user — honored only for non-impersonating admins.

Visibility: non-admins always see only their own events; an impersonating admin sees the target's events; a pure admin sees everything (optionally filtered by user_id).

{
"events": [
{
"id": 42,
"ts": "2026-06-07T12:00:00Z",
"kind": "admin",
"user_id": 2,
"profile_id": 1,
"server": "",
"host": "",
"decision": "profile_create",
"detail": "slug=my-profile"
}
],
"next_before": 42
}

next_before is 0 when there are no further pages.