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:
| Param | Type | Description |
|---|---|---|
communityTag | string | Your community's tag (e.g. your-community, acme). |
Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
limit | integer | 20 | Page size. Default 20, max 100. |
offset | integer | 0 | Zero-based offset for pagination. Use either offset or cursor — not both. |
Response (200):
[
{
"id": "…",
"slug": "…",
"title": "…",
"excerpt": "…",
"publishedAt": "…",
"bannerUrl": "…" // nullable,
"author": { … },
"readTimeMinutes": 0,
"content": "…",
}
]| Field | Type | Description |
|---|---|---|
id | string | Stable article identifier (uuid). |
slug | string | URL-safe identifier; stable across edits. |
title | string | Article title. |
excerpt | string | Short summary shown on list pages. |
publishedAt | string (date-time) | ISO 8601 publish timestamp. Articles only appear in this API once published. |
bannerUrl | string (nullable) | Hero image URL (or null). |
author | object | Author object ({ name, usertag, profileImage }). |
readTimeMinutes | integer | Estimated reading time in minutes. |
content | string | HTML 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:
| Param | Type | Description |
|---|---|---|
communityTag | string | Your community's tag (e.g. your-community, acme). |
Request body:
{
"title": "…",
"content": "…",
"excerpt": "…",
"category": "…",
"bannerUrl": "…",
}| Field | Type | Description |
|---|---|---|
title | string | Article title. Required, non-empty. Used to generate the URL slug. |
content | string | Body as sanitized HTML. Defaults to empty string if omitted. |
excerpt | string | Short summary for listings (max 500 chars). Optional. |
category | string | Free-form category label (max 100 chars). Optional. |
bannerUrl | string | Hero image URL. Required to subsequently PATCH the article to status: PUBLISHED. |
Response (201):
{
"id": "…",
"slug": "…",
"title": "…",
"excerpt": "…",
"publishedAt": "…",
"bannerUrl": "…" // nullable,
"author": { … },
"readTimeMinutes": 0,
"content": "…",
}| Field | Type | Description |
|---|---|---|
id | string | Stable article identifier (uuid). |
slug | string | URL-safe identifier; stable across edits. |
title | string | Article title. |
excerpt | string | Short summary shown on list pages. |
publishedAt | string (date-time) | ISO 8601 publish timestamp. Articles only appear in this API once published. |
bannerUrl | string (nullable) | Hero image URL (or null). |
author | object | Author object ({ name, usertag, profileImage }). |
readTimeMinutes | integer | Estimated reading time in minutes. |
content | string | HTML 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:
| Param | Type | Description |
|---|---|---|
communityTag | string | Your community's tag (e.g. your-community, acme). |
slug | string | URL slug from the event / article. Stable across edits. |
Response (200):
{
"id": "…",
"slug": "…",
"title": "…",
"excerpt": "…",
"publishedAt": "…",
"bannerUrl": "…" // nullable,
"author": { … },
"readTimeMinutes": 0,
"content": "…",
}| Field | Type | Description |
|---|---|---|
id | string | Stable article identifier (uuid). |
slug | string | URL-safe identifier; stable across edits. |
title | string | Article title. |
excerpt | string | Short summary shown on list pages. |
publishedAt | string (date-time) | ISO 8601 publish timestamp. Articles only appear in this API once published. |
bannerUrl | string (nullable) | Hero image URL (or null). |
author | object | Author object ({ name, usertag, profileImage }). |
readTimeMinutes | integer | Estimated reading time in minutes. |
content | string | HTML 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:
| Param | Type | Description |
|---|---|---|
communityTag | string | Your community's tag (e.g. your-community, acme). |
articleId | string | Article identifier (uuid) from POST /articles response. |
Request body:
{
"title": "…",
"content": "…",
"excerpt": "…" // nullable,
"category": "…" // nullable,
"bannerUrl": "…" // nullable,
"status": "…",
}| Field | Type | Description |
|---|---|---|
title | string | New title. Note: changing the title does NOT regenerate the slug — slugs are stable across edits. |
content | string | New HTML body. |
excerpt | string (nullable) | New short summary; null to clear. |
category | string (nullable) | New category; null to clear. |
bannerUrl | string (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. |
status | string (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": "…",
}| Field | Type | Description |
|---|---|---|
id | string | Stable article identifier (uuid). |
slug | string | URL-safe identifier; stable across edits. |
title | string | Article title. |
excerpt | string | Short summary shown on list pages. |
publishedAt | string (date-time) | ISO 8601 publish timestamp. Articles only appear in this API once published. |
bannerUrl | string (nullable) | Hero image URL (or null). |
author | object | Author object ({ name, usertag, profileImage }). |
readTimeMinutes | integer | Estimated reading time in minutes. |
content | string | HTML 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:
| Param | Type | Description |
|---|---|---|
communityTag | string | Your community's tag (e.g. your-community, acme). |
articleId | string | Article 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:
| Status | Body | When |
|---|---|---|
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:
| Param | Type | Description |
|---|---|---|
communityTag | string | Your community's tag (e.g. your-community, acme). |
articleId | string | Article identifier (uuid). |
Request body: none.
Response (200):
Returns the article in its draft state — same shape as POST /articles.
Error responses:
| Status | Body | When |
|---|---|---|
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_..."