Skip to content

HTTP APIs and schema manifest

Every API is JSON over HTTP on the same listener (127.0.0.1:5080 by default). Errors are application/problem+json with type, title, status and detail. This page summarizes the contracts; the full wire contract is docs/design/contracts.md in the repository.

Base path Role Authentication
/authoring/v1 authoring Bearer token on every route except GET /config
/delivery/v1 delivery None for public content
/media/v1, /media/{hash}/… media Bearer token for uploads; files follow the content that uses them
/ with Accept: application/json, /api/episerver/v3.0/… delivery As Delivery
/ext/{name}/… authoring Bearer token; forwarded to an extension’s admin API
/editor/ editor None (static files)

Read-only and cacheable. A request for public content without expansion is one SQL query.

Method and path Result
GET /delivery/v1/route?host=&path=&expand=0|1 The document at that path. host defaults to the Host header.
GET /delivery/v1/content/{id}?locale=&expand=0|1 The document; locale defaults to the master locale.
GET /delivery/v1/content/{id}/children?locale= Published children in order
GET /delivery/v1/preview?token=&expand=0|1 A preview of one version (see Security)
GET /delivery/v1/sites Sites with hosts and enabled locales
  • expand=1 adds "expanded" to every reference and contentArea item, one level deep.
  • Every 200 and 304 carries an ETag and Cache-Control: public, s-maxage=5, stale-while-revalidate=2, or private, no-store when the response includes restricted content. If-None-Match returns 304.
  • A request is served from the site that lists the host, else one that lists *, else the first site by name.
  • Documents outside their publishing window are 404.

The document shape is described in Publishing and projection.

So that an existing headless front-end works without changes:

Request Behaviour
GET /<any path> with Accept: application/json The page at that path in Optimizely’s shape. ?expand=* expands.
GET /api/episerver/v3.0/content/{idOrGuid} with Accept-Language By GUID or by legacy integer ID
GET /api/episerver/v3.0/content/{idOrGuid}/children Children
GET /api/episerver/v3.0/site/ Sites with content roots, languages and hosts

Properties are flattened onto the content, contentLink.id is the legacy integer ID when there is one, URLs are absolute on the site’s primary host, and timestamps have whole seconds, as Optimizely renders them. Known differences: workId is always 0, and rich text is sanitized.

Base path /authoring/v1. Every handler calls the authorization interface. The main routes:

Method and path Purpose
GET /config, GET /me Sign-in mode (no token needed); the caller’s name, roles and isAdmin
GET /schema?scope=, PUT /schema?scope=&dryRun=&force= Read the manifest (with an ETag); replace a scope and get the plan
GET /locales, PUT /locales/{code} Locales
GET /sites, PUT /sites/{id} Sites, start content and hosts
GET /settings/{key}, PUT /settings/{key} Settings such as globalAssetsRoot
POST /content Create content with its first draft
GET /content/{id}?locale=&version= The content view; version is a UUID, latest or published
GET /content?ids=… Up to 200 light nodes in one request
GET /roots, GET /content/{id}/children The tree
GET /search?q=&type=&base=&under= Substring search over names, slugs and file names
GET /drafts?mine= Work in progress
GET /content/{id}/versions Version summaries
PUT /content/{id}/draft?locale= Save a draft (send baseVersionId and baseRev)
POST /content/{id}/publish?locale= Publish the latest version or versionId
POST /content/{id}/schedule, /unschedule, /unpublish Scheduling and unpublishing
POST /content/{id}/ready, /reject Review
POST /content/{id}/move Move, with parentId and position
DELETE /content/{id}, POST /content/{id}/restore, DELETE /content/{id}?purge=true, GET /trash Subtree delete, recycle bin, purge
GET /content/{id}/references Who refers to this content
GET /content/{id}/acl, PUT /content/{id}/acl Access lists
POST /content/{id}/preview-link A signed preview link
POST /content/{id}/fork, /fork/accept, /fork/detach Copies that remember their origin
GET /audit The audit log (admins)
PUT /sources/{id}, GET /sources, POST /sources/{id}/sync, POST /sources/{id}/adopt Upstream sources (overlay)
GET /overlay, POST /content/{id}/overlay/discard, /overlay/accept-upstream Overlay state and actions
POST /import/batch One-shot import
POST /projection/rebuild Rebuild every document
GET /jobs, POST /jobs/{name}/run, PUT /jobs/{name} Jobs
GET|PUT|PATCH|DELETE /extensions/{name}, GET /ui-modules Extensions
GET /tenants, GET /tenants/search, POST /mounts/use, POST /content/{id}/take-over Tenancy

In the content view, data is keyed by JSON field name (mainBody); the server translates to and from field IDs. The view also carries permissions, the overlay state and schemaRevision, so a client knows when its cached schema is stale.

Problems a client branches on carry a stable type:

type Status When
urn:seeamess:problem:locale-missing 404 The content has no version in the requested locale
urn:seeamess:problem:stale-version 409 Someone saved since your baseVersionId / baseRev
urn:seeamess:problem:validation-failed 422 Field values did not validate; errors lists {field, message}
urn:seeamess:problem:breaking-schema-change 409 A schema push with breaking changes and without force
urn:seeamess:problem:upstream-immutable 409 An upstream version would be changed here
urn:seeamess:problem:wrong-status 409 The version’s status does not allow the command
urn:seeamess:problem:delete-blocked 409 You lack Delete on some descendant; blockers lists them
urn:seeamess:problem:parent-deleted 409 Restore without parentId while the parent is deleted
urn:seeamess:problem:other-tenant 409 A command on content another tenant owns
Method and path Purpose
POST /media/v1/upload?parentId=&type=&publish= Upload a file as new media content (X-File-Name header)
PUT /media/v1/content/{id}/blob Replace the file of a media content
POST /media/v1/blobs Store a blob by itself (adapters; admins)
GET /media/{hash}/{fileName} The original, or a rendition with w, h, fit, format, q
GET /globalassets/…, /siteassets/…, /contentassets/… Optimizely-style asset URLs

Blobs are addressed by SHA-256, so originals and renditions are served immutable when public. Renditions without a signature are allowed only for the widths in SEEAMESS_MEDIA_WIDTHS at the default quality; anything else needs sig, signed with SEEAMESS_MEDIA_SECRET. Formats are WebP, JPEG and PNG (AVIF is not built).

Produced by an SDK, accepted by PUT /authoring/v1/schema:

{
"manifestVersion": 1,
"types": [
{
"id": "2066d4d5-0dff-40c0-b42b-cc35e694fa73",
"name": "ArticlePage",
"displayName": "Article",
"description": "A news or blog article.",
"base": "page",
"allowedChildren": [],
"fields": [
{
"id": "<uuid>",
"name": "Summary",
"displayName": "Summary",
"kind": "text",
"localized": true,
"required": false,
"group": "Content",
"order": 10,
"editor": "textarea",
"settings": {}
}
]
}
]
}
  • base: page, block, media, folder. allowedChildren: null means any type, an array lists type IDs.
  • kind: text, richText, bool, int, float, dateTime, url, reference, contentArea, json, opaque. settings.allowedTypes lists type IDs for reference and contentArea.
  • The field name is the C# property name (MainBody); its JSON name in delivery output starts lower case (mainBody).
  • A manifest is refused (422) unless type IDs, type names, field IDs and field JSON names are unique within their scope. Unknown properties are kept.