# Changelog

> Publish and consume product changelog entries.

Source: https://tickos.dev/docs/changelog

Changelog entries are product updates authored in the dashboard or through the API. Reads default to published entries; writes let you manage the full lifecycle (draft, publish, archive, delete). There are two ways to show them publicly: the hosted changelog page in your help center, or your own page fed by this API.

## Hosted changelog page

If your help center has the changelog turned on (Settings > Portal, Changelog module; off by default), TickOS serves a public changelog for you. Only published entries appear, newest first. When the option is off, these URLs return 404. While the help center itself is unpublished, the pages show a "coming soon" screen instead of the entries.

| URL | Description |
|---|---|
| https://{slug}.tickos.dev/changelog | Changelog list (also on your custom help center domain). Loads 20 entries at a time with infinite scroll. |
| https://{slug}.tickos.dev/changelog/{entrySlug} | A single published entry. |
| https://{slug}.tickos.dev/api/portal/{slug}/changelog?offset=0 | Public JSON feed. No API key. Returns { entries, hasMore } with 20 entries per page; pass offset to load the next page. |

Entries in the public JSON feed have id, title, slug, excerpt, content_html, category, version, tags, cover_image_url and published_at.

## Consuming the API on your own site

> **Consumption model (Stripe-style):** The API key is secret and lives only on your backend (server-to-server). Your site calls your own backend (same-origin, no CORS), which proxies to TickOS with the secret key.

## The entry object

All endpoints that return entries use the same fields. content_json is accepted on create and update but is never returned.

| Field | Type | Description |
|---|---|---|
| id | string | Entry ID. |
| title | string | Entry title. |
| slug | string | URL slug, unique within the account. |
| excerpt | string \| null | Short summary. |
| content_html | string \| null | Rendered HTML content. |
| category | string | feature, improvement, fix or announcement. |
| version | string \| null | e.g. v1.4.0. |
| tags | string[] | Labels. |
| cover_image_url | string \| null | Cover image URL. |
| status | string | draft, published or archived. |
| published_at | string \| null | ISO 8601 timestamp of the first publication. null if never published. |

## GET /v1/changelog -- List entries

Permission: read. Returns entries for the account, newest published_at first. By default only published entries (public feed). status and category are not validated here: an unknown value returns an empty list instead of an error.

| Query param | Type | Default | Description |
|---|---|---|---|
| status | string | published | published, draft, archived or all. |
| category | string | -- | feature, improvement, fix, announcement. |
| limit | number | 20 | Max 100. |
| offset | number | 0 | Offset pagination. |

```bash
curl -H "Authorization: Bearer sk_your_api_key" \
  "https://api.tickos.dev/v1/changelog?status=published&limit=20"
```

200 OK:

```json
{
  "data": [
    {
      "id": "...",
      "title": "New block editor",
      "slug": "new-block-editor",
      "excerpt": "Redesigned editor with inline images.",
      "content_html": "<h1>Editor</h1><p>...</p>",
      "category": "feature",
      "version": "v1.4.0",
      "tags": [],
      "cover_image_url": null,
      "status": "published",
      "published_at": "2026-10-01T12:00:00.000Z"
    }
  ],
  "pagination": { "limit": 20, "offset": 0, "total": 1 }
}
```

## GET /v1/changelog/{slug} -- Get one entry

Permission: read. Returns { data: entry } for a single published entry by slug. Responds 404 if it does not exist or is not published (drafts and archived entries are not returned here).

## POST /v1/changelog -- Create an entry

Permission: write. All fields are optional. If status is published, published_at is stamped automatically. Returns 201 with { data: entry }.

| Field | Type | Required | Description |
|---|---|---|---|
| title | string | No | Entry title. Default: "Untitled". |
| slug | string | No | Generated from title if omitted. See slug rules below. |
| excerpt | string | No | Short summary. |
| content_html | string | No | Rendered HTML content. |
| content_json | object | No | Structured editor content. Stored but not returned by the API. |
| category | string | No | feature, improvement, fix, announcement. Default: feature. |
| version | string | No | e.g. v1.4.0. |
| tags | string[] | No | Labels. Ignored if not an array. |
| cover_image_url | string | No | Cover image URL. |
| status | string | No | draft, published, archived. Default: draft. |

- Slug rules on create: the slug you send (or the title) is normalized to lowercase letters, numbers and hyphens (accents removed, max 80 characters). If it is already taken in the account, a suffix is added (-2, -3...). Create never fails with 409 because of the slug; read the final slug from the response.
- Invalid category or status returns 400 with the allowed values.

```bash
curl -X POST https://api.tickos.dev/v1/changelog \
  -H 'Authorization: Bearer sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "New block editor",
    "excerpt": "Redesigned editor with inline images.",
    "content_html": "<h1>Editor</h1><p>...</p>",
    "category": "feature",
    "version": "v1.4.0",
    "status": "published"
  }'
```

## PATCH /v1/changelog/{slug} -- Update or change status

Permission: write. Updates any of the create fields or changes the status (draft/published/archived) of an entry located by slug in any status. Send only the fields you want to change. Returns { data: entry }.

- Publishing stamps published_at only if the entry was never published. Re-publishing an archived or unpublished entry keeps its original published_at.
- A new slug is stored exactly as sent (it is not normalized). Returns 409 if another entry in the account already uses it.
- Invalid category or status returns 400. Unknown entry returns 404.

```bash
# Publish
curl -X PATCH https://api.tickos.dev/v1/changelog/new-block-editor \
  -H 'Authorization: Bearer sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{ "status": "published" }'

# Archive (hide from the public feed)
curl -X PATCH https://api.tickos.dev/v1/changelog/new-block-editor \
  -H 'Authorization: Bearer sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{ "status": "archived" }'
```

> **Archive vs delete:** PATCH with status "archived" removes the entry from the public feed while keeping the content (reversible). DELETE removes it permanently.

## DELETE /v1/changelog/{slug} -- Delete an entry

Permission: write. Permanently deletes an entry (any status) by slug within the account. Returns 404 if it does not exist.

```bash
curl -X DELETE https://api.tickos.dev/v1/changelog/new-block-editor \
  -H 'Authorization: Bearer sk_your_api_key'
```

200 OK:

```json
{
  "deleted": true,
  "slug": "new-block-editor"
}
```

> **Read vs write keys:** Use a read-only key for the proxy that feeds your public changelog, and reserve write keys for automations that create, publish or delete entries. No changelog endpoint needs an admin key.
