Skip to content

CDN API ​

Public API · Enterprise Edition

The CDN delivery endpoint is part of Studio's public, versioned, key-gated API (/api/cdn/v1/) — a stable contract intended for external integrations. CDN is an Enterprise-Edition feature (Starter+ on the managed service; not available in a self-hosted Community deployment).

Studio provides a built-in CDN for delivering content as JSON via API keys. Content is built from Git and stored in Cloudflare R2 object storage.

CDN Settings ​

Get CDN Settings ​

GET /api/workspaces/:workspaceId/projects/:projectId/cdn/settings

Response ​

json
{
  "cdn_enabled": true,
  "cdn_branch": "main"
}

Update CDN Settings ​

PATCH /api/workspaces/:workspaceId/projects/:projectId/cdn/settings
FieldTypeRequiredDescription
cdn_enabledbooleanNoEnable/disable CDN delivery
cdn_branchstring | nullNoBranch the CDN builds from (defaults to the project's default branch)

Returns the updated { cdn_enabled, cdn_branch }.


Builds ​

Trigger Build ​

Trigger a full rebuild of CDN content from the repository. This endpoint claims the project's single in-flight build slot and returns 409 (cdn.build_in_progress) if a build is already running.

POST /api/workspaces/:workspaceId/projects/:projectId/cdn/builds/trigger

Response — SSE progress stream ​

The response is a Server-Sent Events stream, not a single JSON body. Each event is a JSON-encoded progress update with a phase and message (and optional current/total counters):

json
{ "phase": "upload", "message": "Uploading manifest...", "current": 0, "total": 5 }

The stream ends with a terminating complete event carrying the build result:

json
{
  "phase": "complete",
  "message": "Build complete — 12 files in 4200ms",
  "result": {
    "buildId": "uuid",
    "filesUploaded": 12,
    "totalSizeBytes": 52428800,
    "durationMs": 4200,
    "error": null
  }
}

If the build throws, an { "phase": "error", "message": "..." } event is emitted instead. The build process reads content from Git, serializes it to JSON, and uploads to R2. Stale objects are cleaned up via diff-based comparison.

List Builds ​

GET /api/workspaces/:workspaceId/projects/:projectId/cdn/builds

Response ​

json
[
  {
    "id": "uuid",
    "status": "completed",
    "trigger_type": "manual",
    "commit_sha": "abc123",
    "started_at": "2026-01-15T12:00:00Z",
    "completed_at": "2026-01-15T12:00:05Z"
  }
]

API Keys ​

List CDN Keys ​

GET /api/workspaces/:workspaceId/projects/:projectId/cdn/keys

Response ​

json
[
  {
    "id": "uuid",
    "name": "Production Key",
    "key_prefix": "crn_live_7K2mX9p",
    "last_used_at": "2026-01-15T12:00:00Z",
    "created_at": "2026-01-01T00:00:00Z"
  }
]

Create CDN Key ​

POST /api/workspaces/:workspaceId/projects/:projectId/cdn/keys
FieldTypeRequiredDescription
namestringYesKey display name
scopesstring[]NoScopes the key holds. Valid values: delivery, media:read, media:write. Defaults to ['delivery']. Unknown scope strings are rejected with 400.

Response ​

json
{
  "id": "uuid",
  "name": "Production Key",
  "key": "crn_live_7K2mX9pQ4rT6vW3nY8sB1hJ5...",
  "key_prefix": "crn_live_7K2mX9p"
}

WARNING

The full API key is only returned once at creation time. Studio stores only the SHA-256 hash; the key_prefix (first 16 characters) is kept for display. Store the full key securely.

Revoke CDN Key ​

DELETE /api/workspaces/:workspaceId/projects/:projectId/cdn/keys/:keyId

Plan Limits ​

PlanAPI KeysBandwidth/Month
Starter32 GB
Pro1020 GB
EnterpriseUnlimitedUnlimited

Content Delivery (Public) ​

Fetch content through the CDN. Content JSON and manifests are authenticated via API key in the Authorization header. Media binaries (paths under media/) are the one exception: when the project has public media enabled they are served keyless (rate-limited per project + client IP) so a browser <img src> can load them.

GET /api/cdn/v1/:projectId/:path

Headers ​

http
Authorization: Bearer crn_live_your-api-key

Object Paths ​

CDN objects are keyed by model ID (not domain) — the build pipeline writes them flat under content/, meta/, documents/, and models/:

PathReturns
content/{modelId}/{locale}.jsonPublished entries for an i18n collection/singleton/dictionary, by locale (object map keyed by entry ID)
content/{modelId}/data.jsonPublished entries for a non-i18n model
meta/{modelId}/{locale}.jsonPer-entry metadata for an i18n model, by locale (data.json for non-i18n)
documents/{modelId}/{slug}/{locale}.jsonA single document entry ({ frontmatter, body, html })
documents/{modelId}/_index/{locale}.jsonFrontmatter index for a document model, by locale
models/{modelId}.jsonA model definition
models/_index.jsonSummary index of all models
_manifest.jsonBuild manifest (commit SHA, locales, domains, model list)
_media_manifest.jsonMedia manifest — asset metadata (dimensions, blurhash, alt text)
_bundle/{locale}.jsonPer-locale bundle — all JSON content for one locale in a single fetch (see below)
media/{...}Media binaries; served keyless when public media is enabled

Locale bundle (_bundle/{locale}.json) ​

Emitted on every build, a bundle packs all of a locale's JSON content into a single keyed, ETag'd object so a client can prime its per-path cache with one conditional fetch instead of one request per model. Its shape:

json
{
  "version": "1",
  "commitSha": "abc123...",
  "builtAt": "2026-07-10T12:00:00.000Z",
  "locale": "en",
  "paths": {
    "content/authors/en.json": { "a1b2c3d4e5f6": { "name": "Jane" } },
    "content/site-settings/data.json": { "title": "My Site" },
    "documents/articles/_index/en.json": []
  }
}

paths is keyed by the exact delivery paths listed above, and each body is identical to the standalone artifact. All JSON-kind models ship their full body (non-i18n models under content/{modelId}/data.json in every locale bundle); document models contribute only their _index (per-slug bodies and meta/... are not bundled). Consumers must ignore a bundle whose version is not "1" and fall back to per-path fetches.

Examples ​

bash
# Get a collection (English locale)
curl https://studio.example.com/api/cdn/v1/{projectId}/content/blog-post/en.json \
  -H "Authorization: Bearer crn_live_..."

# Get a singleton (English locale)
curl https://studio.example.com/api/cdn/v1/{projectId}/content/hero/en.json \
  -H "Authorization: Bearer crn_live_..."

# Get model definition
curl https://studio.example.com/api/cdn/v1/{projectId}/models/blog-post.json \
  -H "Authorization: Bearer crn_live_..."

Response ​

Returns the raw JSON content file from R2 storage with appropriate Content-Type and ETag headers.

Rate Limiting ​

Keyed CDN requests are rate-limited per API key; keyless public-media requests are rate-limited per project + client IP. Usage (request count and bandwidth) is metered per project — keyless public-media bandwidth counts toward the project's totals under a NULL-key bucket.

Released under the AGPL-3.0 License.