Welcome, First Last

Back To Dashboard

Developer Documentation

Endpoint reference for every public CMS Caddie API — agent (OAuth + Bearer), programmatic (API key), discovery, OAuth 2.1, and MCP.

1. Hosts & auth at a glance

HostWhat it servesAuth
agents.cmscaddie.comAgent API (/...), OAuth, discoveryBearer (cmsk_ agent key OR cmst_ OAuth access token)
mcp.cmscaddie.comMCP server (single user-scoped URL; per-project scopes come from the token's grants)Bearer (same token formats)
api.cmscaddie.comProgrammatic API (legacy API-key endpoints) and dashboardAPI key in header / public
Two token formats: cmsk_ are long-lived agent keys minted from the dashboard. cmst_ are short-lived (1h) OAuth access tokens with paired cmsr_ refresh tokens (90d). Both are accepted on every /... route — the middleware doesn't care which.

2. Discovery

Public, no auth. Cached for 5 minutes (CloudFront).

GEThttps://agents.cmscaddie.com/agent-manifest
Canonical JSON describing every route, scope, request/response shape, publish semantics, and error envelope. Source of truth — OpenAPI and llms.txt are generated from it.
GEThttps://agents.cmscaddie.com/openapi.json
OpenAPI 3.1 projection of the manifest. Drop into Postman, ChatGPT custom GPT, or any OpenAPI-aware framework.
GEThttps://agents.cmscaddie.com/llms.txt
Plain-text route summary following the llmstxt.org convention. Paste into a system prompt or feed to an LLM crawler.
GEThttps://agents.cmscaddie.com/.well-known/ai-plugin.json
ChatGPT-plugin-style manifest pointing at the OpenAPI spec.

3. OAuth 2.1 (PKCE + DCR)

Authorization Code grant with PKCE. Public clients (no secret). Dynamic Client Registration per RFC 7591. Project pinning per RFC 8707 (resource indicators).

GEThttps://agents.cmscaddie.com/.well-known/oauth-authorization-server
RFC 8414 metadata: issuer, endpoints, supported grants/scopes, PKCE methods.
GEThttps://agents.cmscaddie.com/.well-known/oauth-protected-resource[?project_id=<id>]
RFC 9728 protected-resource metadata. Pass project_id to receive a project-pinned resource URL that the client should echo on the authorize request.
POSThttps://agents.cmscaddie.com/oauth/register
Dynamic Client Registration. Body: {client_name, redirect_uris[]}. Returns {client_id, ...}. Public clients only (PKCE required, no secrets issued).
curl -X POST https://agents.cmscaddie.com/oauth/register \
  -H 'Content-Type: application/json' \
  -d '{"client_name":"My MCP Client","redirect_uris":["http://localhost:33333/cb"]}'
GEThttps://agents.cmscaddie.com/oauth/authorize
Authorize endpoint. Renders the email+verification-code login flow, then the consent page. Required query: response_type=code, client_id, redirect_uri, code_challenge, code_challenge_method=S256. Optional: state, scope (space-separated), resource (project-pinned URL) or project_id (direct).
POSThttps://agents.cmscaddie.com/oauth/token
Token exchange. Two grant types:
# authorization_code
curl -X POST https://agents.cmscaddie.com/oauth/token \
  -d 'grant_type=authorization_code&code=<code>&client_id=<id>&redirect_uri=<uri>&code_verifier=<verifier>'

# refresh_token (returns new access + rotated refresh)
curl -X POST https://agents.cmscaddie.com/oauth/token \
  -d 'grant_type=refresh_token&refresh_token=<cmsr_...>&client_id=<id>'

4. MCP (Model Context Protocol)

Streamable HTTP transport. JSON-RPC 2.0 envelope. The server advertises tools auto-generated from the manifest, so new agent endpoints become MCP tools without configuration changes.

POSThttps://mcp.cmscaddie.comBearer
JSON-RPC endpoint. Methods: initialize, tools/list, tools/call, ping. A single URL per user — the token's grants map determines which projects each tool call can target, and the client must pass the project path arg on every call. Returns 401 with WWW-Authenticate on missing token to trigger OAuth discovery.
curl -X POST https://mcp.cmscaddie.com \
  -H 'Authorization: Bearer cmst_...' \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
GEThttps://mcp.cmscaddie.com
Lightweight discovery ping for browsers. Returns server name, version, transport, protocol version.

5. Agent API

All endpoints take Authorization: Bearer <cmsk_... OR cmst_...>. The path's {project} must be one of the projects this token's grants map covers, with the required scope granted on that project. Required scope on each endpoint is shown in the badge.

5a. Groups

GET/projects/{project}/groupscontent:read
List all content groups in a project. Returns {groups: [{id, slug, name, description, public, created}], count}.

5b. Content

GET/projects/{project}/groups/{group}/contentcontent:read
List items in a group. {group} may be the bare slug or the full <project>-<slug> form.
GET/projects/{project}/groups/{group}/content/{id}content:read
Retrieve one item.
GET/projects/{project}/content/search?q=<q>&limit=<n>content:read
Full-text search across all groups in a project.
POST/projects/{project}/groups/{group}/contentcontent:write
Create a content item. Body must include published (YYYYMMDDHHMM, UTC). Setting published ≤ now also requires content:publish.
curl -X POST https://agents.cmscaddie.com/projects/$P/groups/$G/content \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Hello","slug":"hello","published":"999901010000","body":"<p>…</p>"}'
PATCH/projects/{project}/groups/{group}/content/{id}content:write
Merge-patch a content item. Changing published to now-or-past requires content:publish.
DELETE/projects/{project}/groups/{group}/content/{id}content:delete
Delete a content item and fire the delete webhook.

5c. Files

POST/projects/{project}/filesfiles:write
Request a presigned POST for direct-to-S3 upload. Returns {upload: {url, fields}, s3_key, expires_in, max_bytes}.
curl -X POST https://agents.cmscaddie.com/projects/$P/files \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"filename":"spec.pdf","content_type":"application/pdf"}'
DELETE/projects/{project}/filesfiles:delete
Delete a file. Body: {s3_key}. Key must begin with agents/<project>/ — agents cannot touch dashboard-uploaded assets.

6. Programmatic API (API key)

Long-standing endpoints on api.cmscaddie.com for scripts and integrations using a project's API key (managed under Human API Keys in project settings). These predate the agent surface and are kept for backward compatibility.

GEThttps://api.cmscaddie.com/public/{group}
Public read, no auth. Returns published items in a content group flagged public. Format: {group} = <project>-<groupslug>.
GEThttps://api.cmscaddie.com/public/{group}/{hash-key}
Public read of a single item.
GEThttps://api.cmscaddie.com/integration/{group}API key (read)
Authenticated read of all items in a group, including private. Header: Authorization: <api_key>.
GEThttps://api.cmscaddie.com/integration/{group}/{hash-key}API key (read)
Authenticated read of a single item.
POSThttps://api.cmscaddie.com/integration/{group}API key (write)
Create a content item via API key.
POSThttps://api.cmscaddie.com/integration/{group}/{hash-key}API key (write)
Update a content item via API key.
DELETEhttps://api.cmscaddie.com/integration/{group}/{hash-key}API key (write)
Delete a content item via API key.
POSThttps://api.cmscaddie.com/upload/{project}API key
Upload a file via API key. Returns S3 credentials scoped to the project asset path.

7. Conventions

Idempotency

Every write on the agent surface accepts an Idempotency-Key header (recommended: a UUID per logical request). Replays with the same key+body return the cached response. Replays with a different body return 409 conflict. TTL: 24h.

Dry-run

Append ?dry_run=true to any agent write to validate and preview the would-be result without persisting.

Request IDs

Send X-Request-ID if you want to correlate; otherwise the server generates one. The id is echoed in responses (X-Request-ID header and data.request_id / error.request_id) and audit logs.

Content field schema (CRITICAL)

Every content item is shaped:

{
  "published": "YYYYMMDDHHMM",
  "fields": {
    "<field_slug>": { "name": "...", "type": "...", "value": "..." },
    ...
  }
}

Each entry under fields must be a {name, type, value} dict keyed by the field's slug. Bare strings (e.g. "title": "Hello") silently produce broken items. Discover the slugs and types per group via GET /projects/{project}/groups — each group's fields array describes them. Updates follow the same schema; when patching one field, send the full {name, type, value} object for that slug. Field types: plain-text, html, number, image, video, audio, file, code, multiple-files, content::<group_id>. Image/video/file types may include alt.

Publish semantics

published (YYYYMMDDHHMM, UTC)EffectRequired scope
≤ nowLIVE (publishes on next minute tick).content:write + content:publish
> nowScheduled.content:write
Far future (e.g. 999901010000)Effectively unpublished.content:write

Error envelope

{
  "error": {
    "code": "bad_request|unauthorized|forbidden|not_found|conflict|rate_limited|internal|upstream_unavailable",
    "message": "human-readable",
    "retryable": true|false,
    "request_id": "uuid",
    "details": { ... }      // optional, code-specific
  }
}

Success envelope

{ "data": { ... }, "request_id": "uuid" }