Interactive API Docs (Swagger + Stoplight Elements)¶
The AstraPBX API ships a browser-based, try-it-live reference generated from a single OpenAPI spec. This page documents how the docs are served, how the spec stays in sync with the code, and the auth model that the docs default to.
What is served, and where¶
The API server (api/src/server.js) serves the same OpenAPI document three ways:
| Path | What | Engine |
|---|---|---|
/reference | Primary reference — clean three-panel layout | Stoplight Elements (@stoplight/elements@9.0.21, pinned via jsDelivr) |
/api-docs (also /api, /docs) | Classic try-it | Swagger UI (swagger-ui-express) |
/api-spec.json | Raw OpenAPI JSON | served from the in-memory swaggerDocument |
On prod this is devpbx.astradial.com/reference; on staging stagepbx.astradial.com/reference. Both hostnames sit behind Cloudflare Access.
/reference used to be Scalar; it was switched to Stoplight Elements because Scalar's request console always showed Cookies/Headers/Query-Parameters sections that confused integrators and could not be hidden via config.
The spec¶
The source of truth is the hand-written api/docs/API_SPECIFICATION.yaml (OpenAPI 3.0.3). At boot, server.js overrides swaggerDocument.servers (see Try-It and Cloudflare Access).
Current coverage: 124 path keys / 182 operations / 22 tags. Every mounted router group is documented — Authentication, Organization Management, SIP Trunks, DID Numbers, Users, Queues, Greetings, Music on Hold, IVR, Outbound Routes, Call Management, Webhooks, Configuration Management, CRM (/crm/*), API Keys (/api-keys), Ticket Alerts (/orgs/{orgId}/ticket-alerts), WhatsApp Admin (/admin/whatsapp/*), DID Pool (/did-pool/*), and Customer Tunnels (/customer-tunnels/*).
Each endpoint description is one plain sentence (minimalist style); request bodies carry a ready-to-edit example object so "Try It" pre-fills concrete values.
Keeping the spec in sync — check:routes CI¶
api/scripts/check-routes.js runs on every PR that touches api/src/server.js or the spec (workflow Check API routes). It makes two assertions:
- Snapshot — the set of
app.<method>("/path")routes inserver.jsmust matchscripts/routes.snapshot.txtverbatim. Catchesserver.jssilently losing endpoints during a merge. - OpenAPI — every implemented route must appear in the spec under
paths:, and vice-versa, beyond a recorded baseline (scripts/routes.openapi-baseline.json). Only new drift fails.
Important: the OpenAPI check scans both server.js and the mounted routers (src/routes/*.js), resolving each app.use('<mount>', …, <router>) to the router file and prefixing its router.<method>('/sub') paths. Without this, documenting a router-group endpoint (CRM, etc.) would register as "doc-only drift" and fail CI. The snapshot check stays server.js-only.
After an intentional route change: npm run check:routes -- --update regenerates the snapshot + baseline; commit them.
Authentication (what the docs default to)¶
There are two auth systems — see Authentication & API Keys for the full model. In short:
ak_API key →X-API-Keyheader (ApiKeyAuth). Scoped keys created in the editor's API & Webhooks page. This is the simple path — no login, no secret needed. The docs' Authorize dialog now defaults to ApiKeyAuth on every org endpoint (the spec listsApiKeyAuthbeforeBearerAuthin each operation'ssecurity). Admin endpoints keep BearerAuth.org_api_key +api_secret→ JWT viaPOST /auth/login(BearerAuth). The org secret is bcrypt-hashed and can never be read back — only rotated (see below).
Known gap: ak_ permission scoping is not enforced
An ak_ key created with limited permissions (e.g. crm.read only) currently gets full org access — requirePermission() in api/src/middleware/rbac.js checks req.userRole (set only for user JWTs), not req.orgApiKeyPermissions. So today any ak_ key = full org access regardless of the permissions selected. Pre-existing; flagged for a follow-up.
Self-service API-secret rotation¶
Because the org secret is hashed, the only way to obtain a usable one is to rotate it:
| Endpoint | Auth | Use |
|---|---|---|
POST /api/v1/organizations/{id}/regenerate-secret | org (X-API-Key/ak_/JWT), own org only | Self-service — returns {api_key, api_secret} once |
POST /api/v1/admin/organizations/{id}/regenerate-secret | admin JWT | Recovery for an org locked out (lost secret and ak_ key) |
GET /api/v1/admin/organizations/{id}/credentials | admin JWT | Returns api_key only (api_secret: null). No longer mutates |
The GET …/credentials previously regenerated the secret on every GET, so a browser prefetch or a Scalar "Try It" silently rotated the live secret and broke integrations. It is now non-mutating; rotation is POST-only.
Try-It and Cloudflare Access¶
The docs hostnames are behind Cloudflare Access, and the API returns Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true — an invalid combination that browsers block for cross-origin credentialed requests. So:
- The spec's first (default) server is a relative
/api/v1, so "Try It" always calls the same origin that served the docs → no CORS check, and the Cloudflare Access session cookie rides along. (This is why Swagger's try-it worked while an absolute-server Elements try-it failed with a Network Error.) The absolute custom-domain URL is kept as a second server for external tools (Postman). - Stoplight Elements is configured with
tryItCredentialsPolicy="include"so the session cookie is sent.
server.js builds swaggerDocument.servers = [{ url: '/api/v1', … }, { url: 'https://<SWAGGER_DOMAIN>/api/v1', … }]. The old http://<ip>:3000 and localhost servers were removed — they can't work from an HTTPS docs page.
How to use it (operator quickstart)¶
- Editor → API & Webhooks → create an API key → copy the
ak_…. /reference→ Authorize → API Key → paste into X-API-Key.- Open any endpoint → Send API Request. No password/login/token needed.
- For a JWT: rotate the org secret (Organization Management → Rotate secret), then
POST /auth/loginwithapi_key+api_secret.
Source references¶
- Docs serving + servers override:
api/src/server.js(/reference,/api-docs,/api-spec.json, theswaggerDocument.serversblock) - Spec:
api/docs/API_SPECIFICATION.yaml - Route-consistency check:
api/scripts/check-routes.js,scripts/routes.snapshot.txt,scripts/routes.openapi-baseline.json - Secret rotation + credentials:
api/src/server.js(org + admin regenerate-secret handlers)
Related¶
- Authentication & API Keys — the two auth systems in detail
- Webhooks — webhook events, delivery, HMAC, and the test endpoint