Skip to main content

CMS

  • Sells as: the cms product (a product-scoped API key). This capability has no separate product-face contract — its public product route reuses the capability contract below; see the Edge route table's Product face row.

Shared GENERIC CMS engine. Threaded article comments with moderation + a store-authoritative collaborative-edit lock/session engine (multi-worker coherent — the correctness fix over the platform's per-process lock dict) + a user-submission submit/review FSM + (v1.1) a generic typed content model: caller-defined content types, localized immutable revisions, collections, image assets, a publish outbox and a public read of published content. Engagement state is keyed by a caller-supplied (article_id, user_id); everything is org-scoped (organization_id, I6). The engine still never fetches or owns vagary-platform's per-vertical news documents (I4/I5 — those stay platform domain).

  • Group: Content & media
  • Contract: contracts/cms/v1/openapi.yaml
  • Console: Manage the cms product in the console →
  • Runbook: operational checklist for cms — internal (Vagary Labs ops; not part of this public site): docs/runbooks/capability-operations.md#cms
  • Public base: https://api.vagarylabs.com (the consolidated API gateway — one host, per-brand sibling api.<zone>)
  • Auth: a product API key (vgk_…) issued from the console — Authorization: Bearer vgk_…
  • Product face (customer-keyed):
    • https://api.vagarylabs.com/product/v1/cms/comments

Edge route table​

The routes this capability actually serves on the public edge — live-synced from GET https://api.vagarylabs.com/product/v1/_meta/catalog (snapshot v1). The metric is the request-granularity billing counter; the scope is the key permission required.

Product face (customer-keyed)​

MethodPathMetricScope
POST/product/v1/cms/commentscms_operationswrite

Endpoints​

MethodPathSummary
POST/v1/commentsAdd a threaded comment (nested reply via parent_id; max depth 5; max length 5000 chars).
GET/v1/commentsList top-level comments (with nested replies attached) for an article, oldest first.
GET/v1/comments/flaggedThe admin moderation queue — status='flagged', non-deleted, newest first.
GET/v1/comments/countNon-deleted, non-rejected comment count for an article.
DELETE/v1/comments/&#123;comment_id&#125;Soft-delete a comment (admin any; a user only their own).
POST/v1/comments/&#123;comment_id&#125;/moderateAdmin moderation — approve
GET/v1/locks/&#123;article_id&#125;Check the current edit-lock for an article (evicts an expired lock).
POST/v1/locks/&#123;article_id&#125;Acquire an edit lock (store-authoritative, atomic per org+article; granted to the holder or when expired).
DELETE/v1/locks/&#123;article_id&#125;Release an edit lock (only the holder may release).
POST/v1/locks/&#123;article_id&#125;/force-releaseForce-release a lock (admin).
GET/v1/locksAll currently-held locks for the org (evicts expired).
POST/v1/edit-sessionsSave an edit-session audit record (a held lock must be owned by this user).
GET/v1/edit-sessionsEdit history for an article, newest first.
POST/v1/submissionsSubmit an article for editorial review (duplicate non-rejected URL refused).
GET/v1/submissionsList submissions (optionally by status).
POST/v1/submissions/&#123;submission_id&#125;/reviewApprove or reject a pending submission.
GET/v1/submissions/user/&#123;user_id&#125;List a user's submissions.
GET/v1/submissions/&#123;submission_id&#125;Get a single submission by id.
POST/v1/content/typesDefine a content type (a JSON Schema 2020-12 for entry fields). A change appends an immutable new version; an identical definition returns the current version unchanged.
GET/v1/content/typesThe latest version of every content type in the org.
GET/v1/content/types/&#123;type_key&#125;One content type — the latest version, or a pinned version.
POST/v1/content/entriesCreate an entry of a type, optionally with its first revision (validated against the type schema).
GET/v1/content/entriesList entries in the org.
GET/v1/content/entries/&#123;entry_id&#125;One entry with its workflow state, review, published pointer and latest revision per locale.
DELETE/v1/content/entries/&#123;entry_id&#125;Archive an entry. It stops being served; a pending review is closed. Revisions are kept. Publishing again restores it.
POST/v1/content/entries/&#123;entry_id&#125;/revisionsAppend an immutable revision for one locale. Refused while another user holds the edit lock on cms-entry:<entry_id>. Supersedes a pending review.
GET/v1/content/entries/&#123;entry_id&#125;/revisionsRevisions of an entry, newest first.
POST/v1/content/entries/&#123;entry_id&#125;/submitSubmit a draft for review. Opens a request in the submissions FSM, decided through /v1/submissions/{submission_id}/review.
POST/v1/content/entries/&#123;entry_id&#125;/publishPublish the latest revision of every locale atomically and write a cms.entry.published outbox event. An entry in review needs an approved review; a require_review type needs one to publish at all.
GET/v1/content/collectionsCollections in the org.
PUT/v1/content/collections/&#123;collection_key&#125;Create or update a collection's metadata.
GET/v1/content/collections/&#123;collection_key&#125;/itemsEvery item of a collection in rank order, with whether each is live and inside its featured window.
PUT/v1/content/collections/&#123;collection_key&#125;/itemsReplace the ordered item set atomically and write a cms.collection.updated outbox event.
POST/v1/content/assetsUpload a PNG, JPEG, WebP or GIF image of at most 1 MiB, content-addressed by SHA-256 (re-uploading the same bytes returns the existing asset).
GET/v1/content/assetsAsset metadata in the org, newest first (never the bytes).
GET/v1/content/public/&#123;organization_id&#125;/collections/&#123;collection_key&#125;PUBLIC — a collection's live items in rank order, limited to published, non-archived entries inside their featured window, each in the requested locale with fallback.
GET/v1/content/public/&#123;organization_id&#125;/assets/&#123;sha256&#125;PUBLIC — image bytes, only while referenced by a published, non-archived entry. Immutable caching.
GET/v1/content/public/&#123;organization_id&#125;/&#123;type_key&#125;/&#123;entry_key&#125;PUBLIC — the published content of an entry. Locale fallback goes exact tag, shorter prefixes, then the entry's default locale. Drafts, entries in review, archived entries and unpublished locales are never served.
GET/healthLiveness + store/tenancy posture (never the DSN).
GET/metricsPrometheus metrics.

Schemas​

Error​

FieldTypeDescription
errorstringstable machine code (e.g. validation_error, not_found, max_depth)
reasonstringhuman-readable one-line explanation (no secrets/PII)
detailobject
request_idstring

CommentCreate​

FieldTypeDescription
article_idstring
user_idstring
contentstring
parent_idstringparent comment id for a nested reply
site_idstringoptional within-org sub-scope (site_ids array)
organization_idstringonly honored for a trusted first-party caller (anti-spoof)

CommentCreated​

FieldTypeDescription
comment_idstring
createdboolean

Comment​

FieldTypeDescription
comment_idstring
article_idstring
user_idstring
contentstring
parent_idstring
depthinteger
statusstring
deletedboolean
site_idsarray
created_atnumberepoch seconds
repliesarray

CommentList​

FieldTypeDescription
commentsarray

CommentCount​

FieldTypeDescription
article_idstring
countinteger

CommentDelete​

FieldTypeDescription
user_idstring
is_adminboolean
organization_idstring

DeleteResult​

FieldTypeDescription
deletedboolean
reasonstring

Generated by scripts/gen-capability-docs.py from contracts/cms/v1/openapi.yaml — the contract IS the source of truth; edit the contract, not this page. The Edge route table section is synced from the live edge catalog snapshot (v1) — re-sync with python3 scripts/sync-edge-catalog.py.