REST API
Articles

Articles

Articles are long-form content (blog posts, announcements). Each article has a stable slug you can use to deep-link.

Workflow: create as DRAFT via POST /articles, then publish via the dedicated POST /articles/:id/publish action (enforces that bannerUrl is set; returns 409 otherwise). Once published, the article is visible via GET /articles + the article.published webhook fires. POST /articles/:id/unpublish reverses the transition. Both actions are idempotent.

You can also flip status via PATCH /articles/:id — it works, but it skips the bannerUrl invariant and is harder to read in audit logs. Prefer the dedicated actions for CMS-style integrations.

Read endpoints (READ_PUBLIC) — list published, get by slug. Write endpoints (ADMIN) — create, update, publish, unpublish. Author is the community's founder user.

Base URL: https://api.cobuntu.com/api/v1

Every endpoint requires an X-API-Key header. See Authentication for scope details.

GET /communities/{communityTag}/articles — List published articles

Scope: READ_PUBLIC

Path parameters:

ParamTypeDescription
communityTagstringYour community's tag (e.g. your-community, acme).

Query parameters:

ParamTypeDefaultDescription
limitinteger20Page size. Default 20, max 100.
offsetinteger0Zero-based offset for pagination. Use either offset or cursor — not both.

Response (200):

[
  {
      "id": "…",
      "slug": "…",
      "title": "…",
      "excerpt": "…",
      "publishedAt": "…",
      "bannerUrl": "…"  // nullable,
      "author": { … },
      "readTimeMinutes": 0,
      "content": "…",
    }
]
FieldTypeDescription
idstringStable article identifier (uuid).
slugstringURL-safe identifier; stable across edits.
titlestringArticle title.
excerptstringShort summary shown on list pages.
publishedAtstring (date-time)ISO 8601 publish timestamp. Articles only appear in this API once published.
bannerUrlstring (nullable)Hero image URL (or null).
authorobjectAuthor object ({ name, usertag, profileImage }).
readTimeMinutesintegerEstimated reading time in minutes.
contentstringHTML body.

Example:

curl https://api.cobuntu.com/api/v1/communities/my-community/articles \
  -H "X-API-Key: pk_live_..."

POST /communities/{communityTag}/articles — Create an article (DRAFT)

Scope: ADMIN

Path parameters:

ParamTypeDescription
communityTagstringYour community's tag (e.g. your-community, acme).

Request body:

{
  "title": "…",
  "content": "…",
  "excerpt": "…",
  "category": "…",
  "bannerUrl": "…",
}
FieldTypeDescription
titlestringArticle title. Required, non-empty. Used to generate the URL slug.
contentstringBody as sanitized HTML. Defaults to empty string if omitted.
excerptstringShort summary for listings (max 500 chars). Optional.
categorystringFree-form category label (max 100 chars). Optional.
bannerUrlstringHero image URL. Required to subsequently PATCH the article to status: PUBLISHED.

Response (201):

{
  "id": "…",
  "slug": "…",
  "title": "…",
  "excerpt": "…",
  "publishedAt": "…",
  "bannerUrl": "…"  // nullable,
  "author": { … },
  "readTimeMinutes": 0,
  "content": "…",
}
FieldTypeDescription
idstringStable article identifier (uuid).
slugstringURL-safe identifier; stable across edits.
titlestringArticle title.
excerptstringShort summary shown on list pages.
publishedAtstring (date-time)ISO 8601 publish timestamp. Articles only appear in this API once published.
bannerUrlstring (nullable)Hero image URL (or null).
authorobjectAuthor object ({ name, usertag, profileImage }).
readTimeMinutesintegerEstimated reading time in minutes.
contentstringHTML body.

Example:

curl -X POST \
  https://api.cobuntu.com/api/v1/communities/my-community/articles \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ /* request body */ }'

GET /communities/{communityTag}/articles/slug/{slug} — Get article by slug

Scope: READ_PUBLIC

Path parameters:

ParamTypeDescription
communityTagstringYour community's tag (e.g. your-community, acme).
slugstringURL slug from the event / article. Stable across edits.

Response (200):

{
  "id": "…",
  "slug": "…",
  "title": "…",
  "excerpt": "…",
  "publishedAt": "…",
  "bannerUrl": "…"  // nullable,
  "author": { … },
  "readTimeMinutes": 0,
  "content": "…",
}
FieldTypeDescription
idstringStable article identifier (uuid).
slugstringURL-safe identifier; stable across edits.
titlestringArticle title.
excerptstringShort summary shown on list pages.
publishedAtstring (date-time)ISO 8601 publish timestamp. Articles only appear in this API once published.
bannerUrlstring (nullable)Hero image URL (or null).
authorobjectAuthor object ({ name, usertag, profileImage }).
readTimeMinutesintegerEstimated reading time in minutes.
contentstringHTML body.

Example:

curl https://api.cobuntu.com/api/v1/communities/my-community/articles/slug/EXAMPLE_ID \
  -H "X-API-Key: pk_live_..."

PATCH /communities/{communityTag}/articles/{articleId} — Update an article (incl. publish/unpublish)

Scope: ADMIN

Path parameters:

ParamTypeDescription
communityTagstringYour community's tag (e.g. your-community, acme).
articleIdstringArticle identifier (uuid) from POST /articles response.

Request body:

{
  "title": "…",
  "content": "…",
  "excerpt": "…"  // nullable,
  "category": "…"  // nullable,
  "bannerUrl": "…"  // nullable,
  "status": "…",
}
FieldTypeDescription
titlestringNew title. Note: changing the title does NOT regenerate the slug — slugs are stable across edits.
contentstringNew HTML body.
excerptstring (nullable)New short summary; null to clear.
categorystring (nullable)New category; null to clear.
bannerUrlstring (nullable)New banner image; null to clear. PATCH does NOT enforce that bannerUrl is set when transitioning to PUBLISHED — only POST /articles/:id/publish does. If you publish via PATCH without a banner, the article goes live banner-less.
statusstring (enum)Workflow status (DRAFT or PUBLISHED). Transitioning to PUBLISHED fires the article.published webhook and makes the article visible via GET /articles; transitioning back to DRAFT hides it again. No webhook fires on unpublish. For audit-log clarity and the bannerUrl invariant, prefer POST /articles/:id/publish and POST /articles/:id/unpublish.

Response (200):

{
  "id": "…",
  "slug": "…",
  "title": "…",
  "excerpt": "…",
  "publishedAt": "…",
  "bannerUrl": "…"  // nullable,
  "author": { … },
  "readTimeMinutes": 0,
  "content": "…",
}
FieldTypeDescription
idstringStable article identifier (uuid).
slugstringURL-safe identifier; stable across edits.
titlestringArticle title.
excerptstringShort summary shown on list pages.
publishedAtstring (date-time)ISO 8601 publish timestamp. Articles only appear in this API once published.
bannerUrlstring (nullable)Hero image URL (or null).
authorobjectAuthor object ({ name, usertag, profileImage }).
readTimeMinutesintegerEstimated reading time in minutes.
contentstringHTML body.

Example:

curl -X PATCH \
  https://api.cobuntu.com/api/v1/communities/my-community/articles/EXAMPLE_ID \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ /* request body */ }'

POST /communities/{communityTag}/articles/{articleId}/publish — Publish an article

Scope: ADMIN

Transitions the article from DRAFT to PUBLISHED, sets publishedAt, and fires the article.published webhook. Idempotent — calling this on an already-published article returns 200 with the existing row (no second webhook).

Unlike PATCH with status: "PUBLISHED", this endpoint enforces that bannerUrl is set on the article — without one, you get 409 Conflict. This matches the invariant the cobuntu-admin UI enforces; the PATCH route is older and skips the check.

Path parameters:

ParamTypeDescription
communityTagstringYour community's tag (e.g. your-community, acme).
articleIdstringArticle identifier (uuid) from POST /articles response.

Request body: none.

Response (200):

Returns the article in its published state — same shape as GET /articles/slug/{slug}.

{
  "id": "…",
  "slug": "…",
  "title": "…",
  "excerpt": "…",
  "publishedAt": "…",
  "bannerUrl": "…",
  "author": { … },
  "readTimeMinutes": 0,
  "content": "…"
}

Error responses:

StatusBodyWhen
403{"error":"API key does not belong to this community"}API key was issued for a different community than the one in the path.
404{"error":"Community not found"}communityTag doesn't resolve to a community.
404{"error":"Article not found in this community"}articleId doesn't exist or belongs to a different community.
409{"error":"Article must have a bannerUrl before it can be published"}The article's bannerUrl is null. PATCH it with a banner URL first, then retry.

Example:

curl -X POST \
  https://api.cobuntu.com/api/v1/communities/my-community/articles/EXAMPLE_ID/publish \
  -H "X-API-Key: sk_live_..."

POST /communities/{communityTag}/articles/{articleId}/unpublish — Unpublish an article

Scope: ADMIN

Transitions the article from PUBLISHED back to DRAFT. The article disappears from GET /articles immediately. publishedAt is preserved (no data loss) — republishing later keeps the original publish timestamp.

Idempotent — unpublishing a DRAFT article is a no-op (returns the row unchanged).

No webhook fires on unpublish today. If you need a downstream signal when an article is unpublished, poll or wire your own automation. A dedicated article.unpublished event is on the roadmap.

Path parameters:

ParamTypeDescription
communityTagstringYour community's tag (e.g. your-community, acme).
articleIdstringArticle identifier (uuid).

Request body: none.

Response (200):

Returns the article in its draft state — same shape as POST /articles.

Error responses:

StatusBodyWhen
403{"error":"API key does not belong to this community"}API key was issued for a different community.
404{"error":"Community not found"}communityTag doesn't resolve to a community.
404{"error":"Article not found in this community"}articleId doesn't exist or belongs to a different community.

Example:

curl -X POST \
  https://api.cobuntu.com/api/v1/communities/my-community/articles/EXAMPLE_ID/unpublish \
  -H "X-API-Key: sk_live_..."