Welcome, First Last
Back To Dashboard
Endpoint reference for every public CMS Caddie API — agent (OAuth + Bearer), programmatic (API key), discovery, OAuth 2.1, and MCP.
| Host | What it serves | Auth |
|---|---|---|
agents.cmscaddie.com | Agent API (/...), OAuth, discovery | Bearer (cmsk_ agent key OR cmst_ OAuth access token) |
mcp.cmscaddie.com | MCP server (single user-scoped URL; per-project scopes come from the token's grants) | Bearer (same token formats) |
api.cmscaddie.com | Programmatic API (legacy API-key endpoints) and dashboard | API key in header / public |
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.Public, no auth. Cached for 5 minutes (CloudFront).
llms.txt are generated from it.Authorization Code grant with PKCE. Public clients (no secret). Dynamic Client Registration per RFC 7591. Project pinning per RFC 8707 (resource indicators).
project_id to receive a project-pinned resource URL that the client should echo on the authorize request.{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"]}'
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).# 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>'
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.
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"}'
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.
{groups: [{id, slug, name, description, public, created}], count}.{group} may be the bare slug or the full <project>-<slug> form.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>"}'
published to now-or-past requires content:publish.delete webhook.{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"}'
{s3_key}. Key must begin with agents/<project>/ — agents cannot touch dashboard-uploaded assets.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.
{group} = <project>-<groupslug>.Authorization: <api_key>.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.
Append ?dry_run=true to any agent write to validate and preview the would-be result without persisting.
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.
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.
published (YYYYMMDDHHMM, UTC) | Effect | Required scope |
|---|---|---|
| ≤ now | LIVE (publishes on next minute tick). | content:write + content:publish |
| > now | Scheduled. | content:write |
Far future (e.g. 999901010000) | Effectively unpublished. | content:write |
{
"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
}
}
{ "data": { ... }, "request_id": "uuid" }