← All docs
Reference

HTTP API reference

All endpoints sit under `/api/`. Public visitor traffic does NOT go through this server — visitors hit the static artifacts directly via S3+CloudFront, the manifest-pointer L@E resolver, or a customer-owned GitHub repo (depending on `STATICOWL_DEPLOY_TARGET`). The `/api/*` surfac

HTTP API reference

All endpoints sit under /api/. Public visitor traffic does NOT go through this server — visitors hit the static artifacts directly via S3+CloudFront, the manifest-pointer L@E resolver, or a customer-owned GitHub repo (depending on STATICOWL_DEPLOY_TARGET). The /api/* surface is for the admin UI, programmatic clients, MCP servers, and SDK consumers.

Authentication

The CMS uses Cognito ID tokens in an Authorization: Bearer … header. The browser admin (/admin/) uses amazon-cognito-identity-js to obtain and refresh tokens. Programmatic clients obtain a Cognito token via SRP flow or use a CMS-minted API key (prefix gcms_…).

Most endpoints additionally require an X-Site-Id header identifying which site the request applies to. /api/auth/*, /api/health, /api/access/*, /api/sites/* (top-level) and /api/leads are the exceptions.

Auth order (request lifecycle)

request → cognitoAuthMiddleware    → req.auth.{userId, email, name}
        → platformContext          → req.platformUser, req.platformRole
        → requireSiteContext       → req.services.{graph, content, ...}
        → route handler            → service call → engine

See architecture.md for the full picture.

Conventions

Status codes

Code When
200 Success (GET / PATCH / PUT)
201 Resource created (POST)
204 Success, no body (DELETE)
400 Bad input (validation, malformed JSON, business rule violation)
401 Missing or invalid auth
403 Authenticated but no access to the requested site/resource
404 Resource not found
409 Conflict (deploy gate failure, version conflict)
429 Rate limit exceeded
500 Internal server error
503 Memory pressure / overload — request shed
504 Query timeout (default 120s)

Health

GET /api/health

No auth. Returns { status: "ok", lastBuildAt, lastBuildId }.


Access / sites

GET /api/access/my-sites

Returns the sites the current user has access to — used by the admin UI to bootstrap the site picker. No X-Site-Id needed.

[
  { "siteId": "...", "siteName": "Kindatechnical", "graphName": "kindatechnical", "role": "admin" }
]

GET /api/access/site-users (admin only, site-scoped)

Lists users with access to the current site.

POST /api/access/grant (admin only)

Grants a user access to the current site. Body: { userId, role } (role ∈ admin | editor | publisher).

GET /api/sites/

Owner / admin: lists every site on the platform. Other roles: lists only sites the user has access to.

POST /api/sites/

Creates a new site. Grants the creator admin access automatically. Body:

{
  "name": "My Site",
  "publicUrl": "https://example.com",
  "slug": "my-site",
  "starterKit": "developer | author | designer | none",
  "description": "...",
  "mantra": "...",
  "theme": { "primary": "#5A7D4D", "accent": "#FBBF24" }
}

If starterKit is omitted, the user's persona is used. Starter kits seed the new site's graph with default content types + templates.

GET /api/sites/:id (admin / owner / member)

Site detail.

DELETE /api/sites/:id (owner only)

Soft-deletes the site. Engine retains data for the configured retention period.


Auth (site-member management)

These are NOT login/registration — Cognito handles that at the gateway. These manage site members for the current site.

GET /api/auth/users

Lists members of the current site. Requires X-Site-Id.

PUT /api/auth/users/:id/role

Change a member's site role. Body: { role: "admin" | "editor" | "publisher" | "viewer" }.

DELETE /api/auth/users/:id

Revokes the user's access to the current site.

GET /api/auth/api-keys

Lists CMS-minted API keys for the current site (prefix gcms_…). Returns key metadata, not the secret.

POST /api/auth/api-keys

Mints a new key. Body: { name, role }. Response includes the secret once — store it.

DELETE /api/auth/api-keys/:id

Revokes a key.


Content

Content-type names are used as path segments (e.g., article, page).

Method Path Description
GET /content/:type List items. ?q, ?status, ?limit.
POST /content/:type Create item. Body: { fields, relationships? }.
GET /content/:type/:id Get item by full id (type:slug).
PATCH /content/:type/:id Partial update.
DELETE /content/:type/:id Delete item.
POST /content/:type/:id/blocks Append a block to the item's body. Body: { blockType, fields, references?, order? }.
DELETE /content/:type/:id/blocks/:bid Remove a block.
POST /content/:type/:id/publish Transition to published (shortcut for workflow transition).

Content body persists the Editor.js output format: { time, blocks: [{ type, data }], version }.


Content types

Method Path Description
GET /types List content types for the site.
GET /types/:name Get a type definition.
POST /types Create a type. Body: { name, label, fields, relationships? }.
PATCH /types/:name Update a type.

Templates, scripts, queries, routes, views, block-types, workflows, locales, transforms, widgets

Each of these is a CRUD-style schema/asset endpoint:

Resource Path
Templates (rendering) /templates, /templates/:name
Scripts (server-side hooks) /scripts, /scripts/:name
Queries (saved Cypher) /queries, /queries/:name, POST /queries/:name/run
Routes (URL → template/content) /routes, /routes/:slug
Views (admin views) /views, /views/:name, /views/:name/seo
Block types /block-types, /block-types/:name
Workflows /workflows, /workflows/:vvId/state, POST /workflows/:vvId/transition
Locales /locales, /locales/:code, /locales/:code/translations
Transforms (lifecycle hooks) /transforms, /transforms/:id
Widgets /widgets, /widgets/:name

All follow the same shape: GET / lists, GET /:name gets, POST / creates, PUT|PATCH /:name updates. See Lifecycle hooks for transforms specifically.


Releases / Deployments

The /api/releases namespace is where the deploy story lives. Releases are immutable bundles; Deployments point a Release at an environment with a validFrom. See features.md → Releases for concepts.

Environments

Method Path Description
GET /releases/environments List environments for the site.
PUT /releases/environments/:id Upsert an environment (id, name, order, autoBuild, requiresApproval, publicUrlOverride).

Releases

Method Path Description
GET /releases?state=draft|approved|deployed&limit=50 List releases.
POST /releases Create a Release. Body: { name, description, intent, derivedFromReleaseId, includes }.
GET /releases/:id Get release + its INCLUDES.
POST /releases/:id/includes Add a Versionable to a draft release. Body: { kind, versionableId, version, hash, at }.
POST /releases/:id/transition State transition. Body: { state }. Allowed transitions per release-service.ts.

Deployments

Method Path Description
GET /releases/deployments?envId=&limit=50 List recent deployments.
POST /releases/:id/deploy Create a Deployment. Body: { environmentId, intent, validFrom, skipReviewGate? }. Returns 409 if blocking Review findings exist or required reviews are stale.
POST /releases/deployments/:id/rollback Roll back. Body: { rollbackToReleaseId }. Creates a new Deployment fact pointing at the older Release; the rolled-back-from Deployment is marked superseded.

Drip publishing

Schedule N approved Releases across a time window. Each Release gets its own Deployment with a staggered validFrom; the scheduler fires each Deployment at its time. Three patterns: even, random, jittered. Preview-then-approve.

Method Path Description
POST /releases/drip-preview Compute the timestamps without writing anything. Body: { count|releaseIds, windowStart, windowEnd, pattern, maxJitterMs?, minSpacingMs?, seed? }. Returns { schedule, pairs? }.
POST /releases/drip-schedule Create the Deployments. Body: same as preview, plus environmentId. Validates every Release is approved or deployed first — atomic. Returns { schedule, deployments: [...] }.
GET /releases/queue?envId=&limit=100 Future-dated pending Deployments, ordered by validFrom ascending.

See features.md → Drip publishing for examples.

Replay

Method Path Description
GET /releases/replay?envId=&atTime= Returns the Deployment + Release + includes that were live in envId at atTime (defaults to now). The basis for "show me the site as it was on Jan 15."

Build

Method Path Description
POST /build Trigger a build. Body: { env: "dev" }. Returns build status + deploy result.
GET /build/last?env= Last build for an environment.
GET /build/history?env=&limit=50 Recent builds.
POST /build/rollback/:buildId Rollback to a prior build (legacy mode; new mode uses Release rollback).

AI

The /api/ai namespace exposes the AI assistant capabilities. See ai.md for the full feature breakdown.

Common shape:


Media

GET /api/media/stock?provider=pexels|unsplash|pixabay&q=&orientation=landscape

Proxies to the named stock search provider. Returns a normalized { items: [{ id, url, thumb, photographer, source, width, height }] }.

GET /api/media

List uploaded assets for the current site, ordered by upload time (newest first). Query params: ?q=filename-substring, ?countOnly=1 (returns { total } only).

POST /api/media/upload

Multipart form with field file. Accepts images, videos, PDFs. 25 MB max. Returns:

{
  "ok": true, "id": "asset:...", "url": "/media/assets/<sha>.png",
  "sha256": "...", "filename": "...", "mimetype": "image/png",
  "bytes": 12345, "width": 1024, "height": 768,
  "uploadedBy": "user:...", "uploadedAt": "..."
}

Storage: local filesystem (MEDIA_DIR, default /opt/graphiquity/data/cms-media). File's SHA is both the filename and the dedup key.

DELETE /api/media/:id

Removes the Asset node and unlinks the file.

GET /media/assets/<sha>.<ext>

Static file serving. Not under /api/; no auth on reads.


Page kits

Reusable layout fragments (hero sections, CTAs, image galleries, testimonials).

Method Path
GET /page-kits
GET /page-kits/:id
POST /page-kits
PUT /page-kits/:id

Notifications, bugs, leads, export, SDK, agent

These are smaller utility namespaces:


Sample requests

Create + deploy a Release

TOKEN=$(...)
SITE=site:my-site

# 1. Create a draft Release
curl -X POST https://app.staticowl.com/api/releases \
  -H "Authorization: Bearer $TOKEN" -H "X-Site-Id: $SITE" \
  -H "Content-Type: application/json" \
  -d '{"name":"Spring sale","intent":"deploy","includes":[]}'
# → { "id": "release:abc", "state": "draft", ... }

# 2. Add content
curl -X POST .../api/releases/release:abc/includes \
  -d '{"kind":"content","versionableId":"article:welcome","version":3,"at":"2026-04-28T..."}'

# 3. Submit for review
curl -X POST .../api/releases/release:abc/transition -d '{"state":"pending"}'

# 4. (Reviews run...)
# 5. Approve
curl -X POST .../api/releases/release:abc/transition -d '{"state":"approved"}'

# 6. Deploy
curl -X POST .../api/releases/release:abc/deploy \
  -d '{"environmentId":"env:prod","intent":"deploy"}'
# → { "id": "deploy:xyz", "status": "pending" → "deployed", ... }

Schedule 12 posts across an 8-hour window

curl -X POST .../api/releases/drip-preview \
  -d '{
    "releaseIds": ["release:1", "release:2", ..., "release:12"],
    "windowStart": "2026-05-01T08:00:00.000Z",
    "windowEnd":   "2026-05-01T16:00:00.000Z",
    "pattern": "jittered",
    "maxJitterMs": 600000,
    "seed": 42
  }'
# → { schedule: { timestamps: [...] }, pairs: [{releaseId, validFrom}, ...] }

# Operator reviews the calendar, then commits:
curl -X POST .../api/releases/drip-schedule \
  -d '{ ... same body, plus "environmentId": "env:prod" }'

Replay the site as it was on Jan 15

curl ".../api/releases/replay?envId=env:prod&atTime=2026-01-15T15:00:00.000Z" \
  -H "Authorization: Bearer $TOKEN" -H "X-Site-Id: $SITE"
# → { deployment: {...}, release: {...}, includes: [...] }

Env vars reference

See operations.md → Environment variables for the canonical list.