API

API overview

The Alaf Server HTTP API — base URL, authentication, permissions, rate limits, and the standard JSON error shape.

Everything the dashboard and the alaf server CLI can do is a call to the same HTTP API: create projects, trigger deployments, attach domains, read analytics, manage backups. It's a plain JSON API built on Hono, so you can drive it from a script, a CI job, or an AI agent exactly the way the first-party clients do.

Base URL & auth

Every route lives under /api on your own instance — e.g. https://your-host/api/projects. There is no hosted /v1 gateway and no separate key format: the base URL is wherever you run Alaf Server. Authenticate with a personal access token as a bearer header (Authorization: Bearer <token>), created with alaf server token create; the dashboard uses a session cookie instead. Full details in Authentication.

Authentication

A request proves who it is in exactly one of four ways. Alaf Server checks for a Bearer token first, then a session cookie, then the loopback fallback (only if that mode is enabled).

MethodWhat you sendWho uses it
Personal access tokenAuthorization: Bearer opsh_pat_…CLI, scripts, server-to-server
Session cookiehttpOnly cookie set at loginThe dashboard, in a browser
MCP OAuthAn OAuth 2.1 access token bound at consentAI agents connecting to /api/mcp
Zero-auth loopbackNothing (request from 127.0.0.1)Desktop app / opt-in single-user instance
curl https://your-host/api/projects \
  -H "Authorization: Bearer $ALAFSERVER_TOKEN"

Bearer is for non-browser clients only

A Bearer token presented from a browser-trusted origin is rejected (BEARER_NOT_ALLOWED_FROM_BROWSER), so an exfiltrated session token can't be replayed past the httpOnly cookie. A token can be org-scoped (rejects a mismatched X-Organization-Id with TOKEN_ORG_SCOPE) and read-only (rejects mutations with TOKEN_READ_ONLY). MCP OAuth discovery is served at the origin root: /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource.

Permissions

Once you're authenticated, every route runs one permission check. Each route declares a tag of the form resource:actionproject:read, domain:write, deployment:admin — and sub-resources add a middle segment (project:service:write). The action lines up with the HTTP method: read (GET one), write (POST/PUT/PATCH), admin (DELETE and destructive), list (GET a collection).

Access is decided per organization. Alaf Server resolves which org you mean from the X-Organization-Id header (falling back to your session default), then checks your role there — owner, admin, member, or restricted (grant-only). Alaf Server refuses to boot if any route is missing a tag, so there is no accidentally unprotected endpoint. The per-endpoint tables on the module pages list the tag for every route; see Permissions & roles for the full model.

Rate limits

The whole /api surface is rate-limited. Unauthenticated requests fall under a per-IP default; authenticated requests get a more generous per-user budget. Some route groups carry a tighter or looser named policy. Limits are per rolling minute.

PolicyLimitKeyed byApplies to
default-anon100 / minIPAny unauthenticated route
default-authed600 / minuserAny authenticated route
auth-tight10 / minIPPOST /api/auth/* (login, signup, reset)
mcp300 / minIP/api/mcp (tool-call bursts)
webhook-ingress120 / minsource IPInbound webhook deliveries
billing-portal20 / minorgStripe portal / checkout creation

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. /api/health is never limited (load balancers and SSR poll it).

429 — Too many requests

When a bucket is exhausted the request gets a 429 with body {"error":"Too many requests"} plus a Retry-After header (seconds). Back off until the window resets rather than retrying immediately.

Error shape

Errors come back as JSON with a stable shape: a human error message and, for typed failures, a machine code. Validation failures add a field-level details map.

{ "error": "This access token is read-only", "code": "TOKEN_READ_ONLY" }
StatusTypical codeMeaning
400VALIDATION_ERRORRequest body failed the schema — see details for the offending fields.
401INVALID_TOKEN, BEARER_NOT_ALLOWED_FROM_BROWSERNot authenticated, or a bad/expired token (plain Unauthorized when no session at all).
403TOKEN_ORG_SCOPE, TOKEN_READ_ONLYAuthenticated but not allowed — wrong org, read-only token, or your role/grants deny the action.
404Resource doesn't exist, or isn't in your organization (IDOR-safe).
409Conflict with current state (e.g. a delete already in progress).
429Rate-limited (see above).
503AUTH_UNAVAILABLEThe auth backend is unreachable — retry; never treated as "no session".
500Unhandled server error ({"error":"Internal server error"}, no code).

Route groups & availability

Routes are mounted per module. Most exist in every deploy mode; a handful are self-hosted-only (they touch the host filesystem, SSH, or local setup and are never loaded in cloud mode), and the cloud gateway swaps its route set by mode.

Base pathCoversAvailability
/api/healthLiveness + public deploy metadataBoth (unauthenticated)
/api/authSessions, OAuth, org management (Better Auth)Both
/api/projectsProjects, env, deploy configBoth
/api/projects/:id/servicesServices within a projectBoth
/api/deploymentsBuilds, deploys, rollbacksBoth
/api/domainsCustom domains, DNS, SSLBoth
/api/githubGitHub connection, repos, webhooksBoth
/api/webhooksInbound provider webhooks (/api/webhooks/backup too)Both
/api/analyticsTraffic, usage, deployment statsBoth
/api (backups)Backup runs and restoresBoth
/api/backup-destinationsBackup targets, policies, schedulesBoth
/api/notificationsChannels, subscriptions, deliveriesBoth
/api/auditOrganization audit logBoth
/api/settingsWorkspace / deploy defaultsBoth
/api/tokensPersonal access tokensBoth
/api/permissionsRoles and resource grantsBoth
/api/billingPlans, usage, checkoutBoth (mode-specific extensions)
/api/mcpModel Context Protocol JSON-RPC endpointBoth
/api/imagesContainer image helpersBoth
/api/services/terminalInteractive service terminal (WebSocket)Both
/api/cloudCloud account link / gatewayBoth (SaaS vs local route set)
/api/systemFilesystem browse, instance setup, provisioningSelf-hosted only
/api/mailSelf-hosted mail server wizardSelf-hosted only
/api/migrationAdopt an existing Docker hostSelf-hosted only
/api/terminalInteractive server (SSH) terminalSelf-hosted only

Module reference

On this page