# Docs and Changelog API

Scripts and CI jobs can do most of what the Docs and Changelog consoles do: create and configure sites, write and publish pages and entries, upload files, manage domains, read analytics and the activity log. A full API reference for these two products is not published yet. The endpoints on this page are stable and work with an API key today; this page lists them by task, with the scope each one needs.

Paths below are relative to `https://evohub.io`. Requests need an API key, sent as described in [API overview](https://docs.evohub.io/api-overview.md#authentication). Answers use the same envelope as the rest of the API: `{"data": ...}` on success and an `error` object on failure. See [Errors](https://docs.evohub.io/errors.md).

## Scopes

Each endpoint needs one scope. Give a key only the ones its job needs.

| To | Docs | Changelog |
| --- | --- | --- |
| Read sites and everything on them: pages or entries, revisions, analytics, feedback, activity | `docs:site:read` | `changelog:site:read` |
| Create and configure sites: settings, domain, logo, access, versions or categories, redirects, GitHub repository, integrations | `docs:site:write` | `changelog:site:write` |
| Delete a site | `docs:site:delete` | `changelog:site:delete` |
| Write: pages and sections, or entries; files; imports; opening change requests or submitting for review | `docs:page:write` | `changelog:entry:write` |
| Put writing live: publish, unpublish, schedule | `docs:page:publish` | `changelog:entry:publish` |

The activity log of a site, and of the organization, is read with the site read scope.

A key without the scope gets **403** `FORBIDDEN`, never 401. A key sees organization-wide sites and, for a personal key, your teams' sites; an organization key scoped to a team sees that team's. A site it cannot see answers **404**, the same as one that does not exist. See [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md).

## Docs

### Sites

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/docs/sites` | `docs:site:read` | List the sites the key can see |
| GET | `/api/v1/docs/sites/overview` | `docs:site:read` | The list's figures for every site at once |
| POST | `/api/v1/docs/sites` | `docs:site:write` | Create a site |
| GET | `/api/v1/docs/templates` | `docs:site:read` | List the site templates |
| POST | `/api/v1/docs/sites/from-template` | `docs:site:write` | Create a site from a template |
| GET | `/api/v1/docs/starter.zip` | `docs:site:read` | Download the starter project |
| GET | `/api/v1/docs/sites/{siteID}` | `docs:site:read` | Read a site and its settings |
| PATCH | `/api/v1/docs/sites/{siteID}` | `docs:site:write` | Change a site's settings; only the fields you send change |
| DELETE | `/api/v1/docs/sites/{siteID}` | `docs:site:delete` | Delete a site with all its pages and files |
| POST | `/api/v1/docs/sites/{siteID}/publish` | `docs:site:write` | Put the site online or take it offline: `{"published": true}` |
| POST | `/api/v1/docs/sites/{siteID}/preview` | `docs:site:read` | Create a private preview link of the live site |
| POST | `/api/v1/docs/sites/{siteID}/logo` | `docs:site:write` | Upload the logo |
| DELETE | `/api/v1/docs/sites/{siteID}/logo` | `docs:site:write` | Remove the logo |
| POST | `/api/v1/docs/sites/{siteID}/logo/dark` | `docs:site:write` | Upload the dark-mode logo |
| DELETE | `/api/v1/docs/sites/{siteID}/logo/dark` | `docs:site:write` | Remove the dark-mode logo |
| GET | `/api/v1/docs/sites/{siteID}/export` | `docs:site:read` | Download the whole site as a zip |

### Versions and languages

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/docs/sites/{siteID}/versions` | `docs:site:read` | List the site's versions |
| POST | `/api/v1/docs/sites/{siteID}/versions` | `docs:site:write` | Create a version |
| PATCH | `/api/v1/docs/versions/{versionID}` | `docs:site:write` | Change a version |
| DELETE | `/api/v1/docs/versions/{versionID}` | `docs:site:write` | Delete a version |
| GET | `/api/v1/docs/sites/{siteID}/translations` | `docs:site:read` | How far each language's translation has come |
| GET | `/api/v1/docs/pages/{pageID}/translations` | `docs:site:read` | List a page's translations |
| POST | `/api/v1/docs/pages/{pageID}/translations` | `docs:page:write` | Start a translation of a page |

### Sections and pages

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/docs/sites/{siteID}/tree` | `docs:site:read` | The navigation: sections and pages in order |
| PUT | `/api/v1/docs/sites/{siteID}/order` | `docs:page:write` | Reorder the navigation, and move pages between sections |
| POST | `/api/v1/docs/sites/{siteID}/sections` | `docs:page:write` | Create a section |
| PATCH | `/api/v1/docs/sections/{sectionID}` | `docs:page:write` | Rename a section |
| DELETE | `/api/v1/docs/sections/{sectionID}` | `docs:page:write` | Delete a section |
| POST | `/api/v1/docs/sites/{siteID}/pages` | `docs:page:write` | Create a page; it starts as a draft |
| GET | `/api/v1/docs/pages/{pageID}` | `docs:site:read` | Read a page: its draft and what is live |
| PATCH | `/api/v1/docs/pages/{pageID}` | `docs:page:write` | Edit a page's draft |
| DELETE | `/api/v1/docs/pages/{pageID}` | `docs:page:write` | Delete a page; a published one also needs `docs:page:publish` |
| POST | `/api/v1/docs/pages/{pageID}/git/resolve` | `docs:page:write` | Settle a conflict between the page and its GitHub file |

### Publishing

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| POST | `/api/v1/docs/pages/{pageID}/publish` | `docs:page:publish` | Publish the page's draft |
| POST | `/api/v1/docs/pages/{pageID}/unpublish` | `docs:page:publish` | Take the page off the live site |

On a site that requires review, publishing goes through change requests and these answer **409** `REVIEW_REQUIRED`. Open a change request instead (below); a person approves and merges it.

### Revisions

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/docs/pages/{pageID}/revisions` | `docs:site:read` | List a page's revisions, newest first |
| GET | `/api/v1/docs/pages/{pageID}/revisions/{revisionID}` | `docs:site:read` | Read one revision |
| POST | `/api/v1/docs/pages/{pageID}/revisions/{revisionID}/restore` | `docs:page:write` | Put a revision back into the draft |
| POST | `/api/v1/docs/pages/{pageID}/revisions/{revisionID}/rollback` | `docs:page:write` | Make an earlier publish live again; also needs `docs:page:publish`, except on a site that requires review, where it opens a change request instead |

### Change requests

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/docs/sites/{siteID}/changes` | `docs:site:read` | What is waiting to go live |
| GET | `/api/v1/docs/sites/{siteID}/change-requests` | `docs:site:read` | List change requests |
| POST | `/api/v1/docs/sites/{siteID}/change-requests` | `docs:page:write` | Open a change request |
| GET | `/api/v1/docs/change-requests/{crID}` | `docs:site:read` | Read a change request |
| PATCH | `/api/v1/docs/change-requests/{crID}` | `docs:page:write` | Change its title, description, items or reviewers |
| GET | `/api/v1/docs/change-requests/{crID}/diff` | `docs:site:read` | What it changes, against the live site |
| POST | `/api/v1/docs/change-requests/{crID}/close` | `docs:page:write` | Close it without merging |
| POST | `/api/v1/docs/change-requests/{crID}/reopen` | `docs:page:write` | Reopen a closed one |
| POST | `/api/v1/docs/change-requests/{crID}/refresh` | `docs:page:write` | Bring its items up to date with their drafts |
| GET | `/api/v1/docs/change-requests/{crID}/comments` | `docs:site:read` | List its comments |
| POST | `/api/v1/docs/change-requests/{crID}/comments` | `docs:page:write` or `docs:page:publish` | Comment on it |
| DELETE | `/api/v1/docs/comments/{commentID}` | `docs:site:read` | Delete one of the key's own comments |
| POST | `/api/v1/docs/change-requests/{crID}/preview` | `docs:page:write` | Create a preview link of the change request |
| DELETE | `/api/v1/docs/change-requests/{crID}/preview` | `docs:page:write` | Withdraw its preview link |

### Snippets and API references

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/docs/sites/{siteID}/snippets` | `docs:site:read` | List reusable snippets |
| POST | `/api/v1/docs/sites/{siteID}/snippets` | `docs:page:write` | Create a snippet |
| GET | `/api/v1/docs/snippets/{snippetID}` | `docs:site:read` | Read a snippet |
| PATCH | `/api/v1/docs/snippets/{snippetID}` | `docs:page:write` | Edit a snippet's draft |
| DELETE | `/api/v1/docs/snippets/{snippetID}` | `docs:page:write` | Delete a snippet |
| POST | `/api/v1/docs/snippets/{snippetID}/publish` | `docs:page:publish` | Publish a snippet |
| POST | `/api/v1/docs/snippets/{snippetID}/unpublish` | `docs:page:publish` | Unpublish a snippet |
| GET | `/api/v1/docs/sites/{siteID}/api-specs` | `docs:site:read` | List API references |
| POST | `/api/v1/docs/sites/{siteID}/api-specs` | `docs:page:write` | Add an API reference from an OpenAPI document |
| GET | `/api/v1/docs/api-specs/{specID}` | `docs:site:read` | Read an API reference |
| PATCH | `/api/v1/docs/api-specs/{specID}` | `docs:page:write` | Change an API reference's settings |
| DELETE | `/api/v1/docs/api-specs/{specID}` | `docs:page:write` | Delete an API reference |
| GET | `/api/v1/docs/api-specs/{specID}/file` | `docs:site:read` | Download its OpenAPI document |
| PUT | `/api/v1/docs/api-specs/{specID}/file` | `docs:page:write` | Replace its OpenAPI document, for example from CI |
| POST | `/api/v1/docs/api-specs/{specID}/publish` | `docs:page:publish` | Publish the API reference |
| POST | `/api/v1/docs/api-specs/{specID}/unpublish` | `docs:page:publish` | Unpublish the API reference |

### Files and redirects

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/docs/sites/{siteID}/assets` | `docs:site:read` | List uploaded files |
| POST | `/api/v1/docs/sites/{siteID}/assets` | `docs:page:write` | Upload a file |
| DELETE | `/api/v1/docs/assets/{assetID}` | `docs:page:write` | Delete a file |
| GET | `/api/v1/docs/sites/{siteID}/redirects` | `docs:site:read` | List redirects |
| POST | `/api/v1/docs/sites/{siteID}/redirects` | `docs:site:write` | Create a redirect |
| PATCH | `/api/v1/docs/redirects/{redirectID}` | `docs:site:write` | Change a redirect |
| DELETE | `/api/v1/docs/redirects/{redirectID}` | `docs:site:write` | Delete a redirect |

### Import and GitHub

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| POST | `/api/v1/docs/imports` | `docs:page:write` | Upload content from another tool and get a preview |
| GET | `/api/v1/docs/imports/{importID}` | `docs:site:read` | Read an import and its preview |
| POST | `/api/v1/docs/imports/{importID}/commit` | `docs:page:write` | Import it into a site |
| DELETE | `/api/v1/docs/imports/{importID}` | `docs:page:write` | Discard an import |
| GET | `/api/v1/docs/git/config` | `docs:site:read` | Whether GitHub sync is available |
| GET | `/api/v1/docs/sites/{siteID}/git` | `docs:site:read` | The site's GitHub connection |
| GET | `/api/v1/docs/sites/{siteID}/git/repos` | `docs:site:write` | Repositories the connection can use |
| GET | `/api/v1/docs/sites/{siteID}/git/branches` | `docs:site:write` | Branches of a repository |
| PUT | `/api/v1/docs/sites/{siteID}/git` | `docs:site:write` | Choose the repository, branch, folder and format |
| DELETE | `/api/v1/docs/sites/{siteID}/git` | `docs:site:write` | Disconnect the site from GitHub |
| POST | `/api/v1/docs/sites/{siteID}/git/sync` | `docs:page:write` | Sync from the repository now |

Connecting a site to GitHub in the first place is done by a person in the console. See [GitHub sync](https://docs.evohub.io/github-sync.md) and [Import and export](https://docs.evohub.io/import-and-export.md).

### Domain and access

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/docs/sites/{siteID}/domain` | `docs:site:read` | The custom domain and its verification state |
| PUT | `/api/v1/docs/sites/{siteID}/domain` | `docs:site:write` | Set the custom domain |
| DELETE | `/api/v1/docs/sites/{siteID}/domain` | `docs:site:write` | Remove the custom domain |
| GET | `/api/v1/docs/sites/{siteID}/access` | `docs:site:read` | Who may read the published site |
| PUT | `/api/v1/docs/sites/{siteID}/access` | `docs:site:write` | Change who may read it |
| PUT | `/api/v1/docs/sites/{siteID}/access/password` | `docs:site:write` | Set the site's password |
| POST | `/api/v1/docs/sites/{siteID}/access/jwt/verify` | `docs:site:write` | Check a reader token against the site's settings |
| POST | `/api/v1/docs/sites/{siteID}/access/sign-out` | `docs:site:write` | Sign every reader out |
| GET | `/api/v1/docs/sites/{siteID}/access/read-tokens` | `docs:site:write` | List read tokens |
| DELETE | `/api/v1/docs/sites/{siteID}/access/read-tokens/{tokenID}` | `docs:site:write` | Revoke a read token |

See [Custom domain](https://docs.evohub.io/docs-custom-domain.md) and [Private docs](https://docs.evohub.io/private-docs.md).

### Analytics, feedback and link health

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/docs/analytics` | `docs:site:read` | Analytics for every site the key can see |
| GET | `/api/v1/docs/sites/{siteID}/analytics` | `docs:site:read` | A site's page views and readers |
| GET | `/api/v1/docs/sites/{siteID}/analytics/search` | `docs:site:read` | What readers searched for |
| GET | `/api/v1/docs/sites/{siteID}/analytics/ai` | `docs:site:read` | Visits from AI assistants and crawlers |
| GET | `/api/v1/docs/sites/{siteID}/analytics/gaps` | `docs:site:read` | Searches that found nothing |
| GET | `/api/v1/docs/sites/{siteID}/analytics/export.csv` | `docs:site:read` | Download analytics as CSV |
| GET | `/api/v1/docs/sites/{siteID}/feedback` | `docs:site:read` | List readers' feedback |
| PATCH | `/api/v1/docs/feedback/{feedbackID}` | `docs:page:write` | Resolve or reopen feedback |
| DELETE | `/api/v1/docs/feedback/{feedbackID}` | `docs:page:write` | Delete feedback |
| GET | `/api/v1/docs/sites/{siteID}/health` | `docs:site:read` | The live site's link report |
| POST | `/api/v1/docs/sites/{siteID}/health/check` | `docs:page:write` | Check the links again now |

### Activity log

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/docs/activity` | `docs:site:read` | What happened across every site the key can see |
| GET | `/api/v1/docs/sites/{siteID}/activity` | `docs:site:read` | What happened on one site |

## Changelog

### Sites

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/changelog/sites` | `changelog:site:read` | List the changelogs the key can see |
| GET | `/api/v1/changelog/sites/overview` | `changelog:site:read` | The list's figures for every changelog at once |
| POST | `/api/v1/changelog/sites` | `changelog:site:write` | Create a changelog |
| GET | `/api/v1/changelog/sites/{siteID}` | `changelog:site:read` | Read a changelog and its settings |
| PATCH | `/api/v1/changelog/sites/{siteID}` | `changelog:site:write` | Change its settings; only the fields you send change |
| DELETE | `/api/v1/changelog/sites/{siteID}` | `changelog:site:delete` | Delete a changelog with every entry on it |
| POST | `/api/v1/changelog/sites/{siteID}/publish` | `changelog:site:write` | Put the changelog online or take it offline: `{"published": true}` |
| POST | `/api/v1/changelog/sites/{siteID}/preview` | `changelog:site:read` | Create a private preview link of the live changelog |
| POST | `/api/v1/changelog/sites/{siteID}/logo` | `changelog:site:write` | Upload the logo |
| DELETE | `/api/v1/changelog/sites/{siteID}/logo` | `changelog:site:write` | Remove the logo |
| POST | `/api/v1/changelog/sites/{siteID}/logo/dark` | `changelog:site:write` | Upload the dark-mode logo |
| DELETE | `/api/v1/changelog/sites/{siteID}/logo/dark` | `changelog:site:write` | Remove the dark-mode logo |

### Categories

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/changelog/sites/{siteID}/categories` | `changelog:site:read` | List categories |
| POST | `/api/v1/changelog/sites/{siteID}/categories` | `changelog:site:write` | Create a category |
| PUT | `/api/v1/changelog/sites/{siteID}/categories/order` | `changelog:site:write` | Reorder categories |
| PATCH | `/api/v1/changelog/categories/{categoryID}` | `changelog:site:write` | Change a category |
| DELETE | `/api/v1/changelog/categories/{categoryID}` | `changelog:site:write` | Delete a category |

### Entries

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/changelog/sites/{siteID}/entries` | `changelog:site:read` | List entries; filter with `status`, `category`, `tag` and `q` |
| POST | `/api/v1/changelog/sites/{siteID}/entries` | `changelog:entry:write` | Create an entry; it starts as a draft |
| GET | `/api/v1/changelog/entries/{entryID}` | `changelog:site:read` | Read an entry with its review state |
| PATCH | `/api/v1/changelog/entries/{entryID}` | `changelog:entry:write` | Edit an entry's draft |
| DELETE | `/api/v1/changelog/entries/{entryID}` | `changelog:entry:write` | Delete an entry; a published one also needs `changelog:entry:publish` |

### Publishing

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| POST | `/api/v1/changelog/entries/{entryID}/submit` | `changelog:entry:write` | Submit the draft for review |
| POST | `/api/v1/changelog/entries/{entryID}/publish` | `changelog:entry:publish` | Publish the draft now |
| POST | `/api/v1/changelog/entries/{entryID}/schedule` | `changelog:entry:publish` | Publish at a later time: `{"publish_at": "…"}` |
| POST | `/api/v1/changelog/entries/{entryID}/unschedule` | `changelog:entry:publish` | Cancel a scheduled publish |
| POST | `/api/v1/changelog/entries/{entryID}/unpublish` | `changelog:entry:publish` | Take the entry off the changelog |
| POST | `/api/v1/changelog/entries/{entryID}/git/resolve` | `changelog:entry:publish` | Settle a conflict between the entry and its GitHub file |

On a changelog that requires review, publishing and scheduling need the approvals first and otherwise answer **409** `REVIEW_REQUIRED`. A key can submit an entry; a person approves it. See [Writing entries](https://docs.evohub.io/writing-entries.md).

### Revisions and images

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/changelog/entries/{entryID}/revisions` | `changelog:site:read` | List an entry's revisions |
| GET | `/api/v1/changelog/entries/{entryID}/revisions/{revisionID}` | `changelog:site:read` | Read one revision |
| POST | `/api/v1/changelog/entries/{entryID}/revisions/{revisionID}/restore` | `changelog:entry:write` | Put a revision back into the draft |
| GET | `/api/v1/changelog/sites/{siteID}/assets` | `changelog:site:read` | List uploaded images |
| POST | `/api/v1/changelog/sites/{siteID}/assets` | `changelog:entry:write` | Upload an image |
| DELETE | `/api/v1/changelog/assets/{assetID}` | `changelog:entry:write` | Delete an image |

### Subscribers

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/changelog/sites/{siteID}/email` | `changelog:site:read` | How email goes out to subscribers |
| GET | `/api/v1/changelog/sites/{siteID}/subscribers` | `changelog:site:read` | List subscribers |
| GET | `/api/v1/changelog/sites/{siteID}/subscribers/export.csv` | `changelog:site:write` | Download subscribers as CSV |
| DELETE | `/api/v1/changelog/subscribers/{subscriberID}` | `changelog:site:write` | Remove a subscriber |

### Slack and webhooks

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/changelog/chat-channels` | `changelog:site:write` | Chat app channels an integration can post to |
| GET | `/api/v1/changelog/sites/{siteID}/webhooks` | `changelog:site:write` | List integrations |
| POST | `/api/v1/changelog/sites/{siteID}/webhooks` | `changelog:site:write` | Create an integration |
| PATCH | `/api/v1/changelog/webhooks/{webhookID}` | `changelog:site:write` | Change an integration |
| DELETE | `/api/v1/changelog/webhooks/{webhookID}` | `changelog:site:write` | Delete an integration |
| POST | `/api/v1/changelog/webhooks/{webhookID}/test` | `changelog:site:write` | Send a test delivery |
| POST | `/api/v1/changelog/webhooks/{webhookID}/rotate-secret` | `changelog:site:write` | Replace the signing secret |
| GET | `/api/v1/changelog/webhooks/{webhookID}/deliveries` | `changelog:site:write` | Recent deliveries and their results |

See [Reaching readers](https://docs.evohub.io/reaching-readers.md).

### GitHub

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/changelog/git/config` | `changelog:site:read` | Whether GitHub sync is available |
| GET | `/api/v1/changelog/sites/{siteID}/git` | `changelog:site:read` | The changelog's GitHub connection |
| GET | `/api/v1/changelog/sites/{siteID}/git/repos` | `changelog:site:write` | Repositories the connection can use |
| GET | `/api/v1/changelog/sites/{siteID}/git/branches` | `changelog:site:write` | Branches of a repository |
| PUT | `/api/v1/changelog/sites/{siteID}/git` | `changelog:site:write` | Choose the repository and branch |
| DELETE | `/api/v1/changelog/sites/{siteID}/git` | `changelog:site:write` | Disconnect from GitHub |
| POST | `/api/v1/changelog/sites/{siteID}/git/sync` | `changelog:entry:write` | Sync from the repository now |
| POST | `/api/v1/changelog/sites/{siteID}/imports/github` | `changelog:entry:write` | Import GitHub releases as entries |

Connecting a changelog to GitHub in the first place is done by a person in the console. See [GitHub](https://docs.evohub.io/changelog-github.md).

### Domain and access

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/changelog/sites/{siteID}/domain` | `changelog:site:read` | The custom domain and its verification state |
| PUT | `/api/v1/changelog/sites/{siteID}/domain` | `changelog:site:write` | Set the custom domain |
| DELETE | `/api/v1/changelog/sites/{siteID}/domain` | `changelog:site:write` | Remove the custom domain |
| GET | `/api/v1/changelog/sites/{siteID}/access` | `changelog:site:read` | Who may read the changelog |
| PUT | `/api/v1/changelog/sites/{siteID}/access` | `changelog:site:write` | Change who may read it |
| PUT | `/api/v1/changelog/sites/{siteID}/access/password` | `changelog:site:write` | Set the changelog's password |
| POST | `/api/v1/changelog/sites/{siteID}/access/sign-out` | `changelog:site:write` | Sign every reader out |

### Analytics and feedback

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/changelog/analytics` | `changelog:site:read` | Analytics for every changelog the key can see |
| GET | `/api/v1/changelog/sites/{siteID}/analytics` | `changelog:site:read` | A changelog's views and readers |
| GET | `/api/v1/changelog/sites/{siteID}/analytics/export.csv` | `changelog:site:read` | Download analytics as CSV |
| GET | `/api/v1/changelog/sites/{siteID}/feedback` | `changelog:site:read` | List readers' feedback |
| PATCH | `/api/v1/changelog/feedback/{feedbackID}` | `changelog:site:write` | Resolve or reopen feedback |
| DELETE | `/api/v1/changelog/feedback/{feedbackID}` | `changelog:site:write` | Delete feedback |

### Activity log

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `/api/v1/changelog/activity` | `changelog:site:read` | What happened across every changelog the key can see |
| GET | `/api/v1/changelog/sites/{siteID}/activity` | `changelog:site:read` | What happened on one changelog |

## Create and publish a changelog entry

A release pipeline can write the entry and publish it in two calls. The key needs `changelog:entry:write` and `changelog:entry:publish`.

```bash
# 1. Create the entry (a draft)
curl https://evohub.io/api/v1/changelog/sites/$SITE_ID/entries \
  -H "Authorization: Bearer $EVOHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Faster exports",
    "summary": "CSV exports now finish in seconds.",
    "body_md": "Exports of large projects used to take minutes. They now stream as they are built.",
    "version": "2.4.0",
    "category_ids": ["'"$CATEGORY_ID"'"],
    "tags": ["exports"]
  }'

# 2. Publish it, and tell subscribers and integrations
curl -X POST https://evohub.io/api/v1/changelog/entries/$ENTRY_ID/publish \
  -H "Authorization: Bearer $EVOHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"notify": true}'
```

`title` is required; a blank `slug` is made from the title. The answer to the first call is the entry, with its `id`. `notify` is optional; leave it out to keep the entry's setting. To publish later instead, call `/schedule` with `{"publish_at": "2026-12-01T09:00:00Z"}`. Entry fields also include `cover_image_url`, `pinned`, `author_name` and `date`.

## Update and publish a docs page

```bash
# 1. Edit the draft; only the fields you send change
curl -X PATCH https://evohub.io/api/v1/docs/pages/$PAGE_ID \
  -H "Authorization: Bearer $EVOHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "7"' \
  -d '{"title": "Install the CLI", "body_md": "Run `brew install acme` and sign in."}'

# 2. Publish the draft
curl -X POST https://evohub.io/api/v1/docs/pages/$PAGE_ID/publish \
  -H "Authorization: Bearer $EVOHUB_API_KEY"
```

Every save answers with an `ETag` holding the page's version. Send it back as `If-Match` (or as `version` in the body) and a save made over someone else's newer edit is refused with **409** `EDIT_CONFLICT`, carrying the page as it now stands; leave both out to overwrite. A page's section is changed with `PUT /api/v1/docs/sites/{siteID}/order`, not on the page. To create a page, `POST /api/v1/docs/sites/{siteID}/pages` with `title`, and optionally `slug`, `body_md` and `section_id`; `?version=` picks the docs version, and the default version is used without it.

On a site that requires review, step 2 answers **409** `REVIEW_REQUIRED`: open a change request for the page with `POST /api/v1/docs/sites/{siteID}/change-requests` and a `title`, and a person approves and merges it.

## What is not part of the API

- **Reserved for a person.** Approving, requesting changes on, merging and reverting docs change requests and approving changelog entries; connecting a docs site or a changelog to GitHub; and issuing private docs credentials (a new reader-token signing secret, a test token, a read token) are done by a person in the console. A key gets **403** `PERSON_REQUIRED`. See [What a key can never do](https://docs.evohub.io/api-keys-and-scopes.md#what-a-key-can-never-do).
- **Public-site routes.** The addresses under `/api/v1/docs/public/` and `/api/v1/changelog/public/` serve your published sites to their visitors. They are not part of the API, may change without notice, and do not take an API key.
- **Console-only helpers** such as the console home, notifications and who-is-editing indicators are left out here.

## Related

- [API overview](https://docs.evohub.io/api-overview.md)
- [API keys and scopes](https://docs.evohub.io/api-keys-and-scopes.md)
- [Errors](https://docs.evohub.io/errors.md)
- [Docs overview](https://docs.evohub.io/docs-overview.md)
- [Review and change requests](https://docs.evohub.io/review-and-change-requests.md)
- [Changelog overview](https://docs.evohub.io/changelog-overview.md)
