# Sokuho — agent setup

Sokuho publishes product updates ("changelogs") that customers actually read. Each **project** is one public updates page on its own subdomain, with markdown **posts** organized by **categories** and colored **labels** (New / Improvement / Fix by default). Posts can be drafted, scheduled, backdated, and published through MCP or the REST API.

This page is written for AI agents. If you are an agent setting up Sokuho for a human: follow the quickstart top to bottom. Everything a human can do in the UI, you can do here.

## Etiquette (read first)

- **Default to drafts.** Create posts as drafts and ask your human before publishing — publishing is customer-visible.
- **Never invent product news.** Only write about changes your human described or that you verified (e.g. from a merged PR they pointed you at).
- **Match the voice before writing.** Fetch the project (its `voice_prompt` is the standing style instruction) and read the last few published posts (`GET .../posts?status=published&bodies=true`) as style reference — tone, length, how much detail. You are the writer; these are your brief.
- If no `voice_prompt` is set, propose one based on your human's existing writing and save it with a project update so every future post — yours or another agent's — sounds the same.

## Connect over MCP

The remote Streamable HTTP server is at `https://sokuho.emilkarl.dev/mcp`. Claude, ChatGPT, and other OAuth-capable MCP clients can discover authentication automatically from that URL. The authorization flow uses OAuth authorization code + PKCE and requests the `mcp` scope.

For hand-configured clients such as Claude Code, the same personal `soku_...` API token described below can be used as a Bearer token for `/mcp`.

Available tools:
- `list_projects`, `create_project`, `update_project`
- `list_posts`, `get_post`, `create_post`, `update_post`
- `generate_draft` — hand it raw material (a PR description, commits, or notes) and the server writes the draft in the project's configured voice, using its recent posts as style reference. Prefer this over create_post when the project has a `voice_prompt`.
- `publish_post`, `unpublish_post`
- `upload_image` — base64 image bytes in, a markdown snippet out. Pass `frame: true` for screenshots: the server turns them into presentation images (rounded corners, shadow, padding on the project's accent color). Put the returned markdown in the post body.

The write path is deliberately draft-first: neither `create_post` nor `generate_draft` can publish. Publishing requires a separate `publish_post` call after human approval.

## Quickstart

**1. Get a token (human handoff — one time).**
Signup is invite-only. Ask your human to:
  - create an account at https://sokuho.emilkarl.dev/registration/new (they need an invite code), then
  - create an API token at https://sokuho.emilkarl.dev/api_tokens and paste it to you.

Use it on every request: `Authorization: Bearer soku_...`

Prefer the terminal? The `sokuho` CLI wraps this API — see [CLI](#cli) below.

**2. Orient yourself.**

```bash
curl -s https://sokuho.emilkarl.dev/api/v1/me -H "Authorization: Bearer $SOKUHO_TOKEN"
```

Returns the user, their accounts, and existing projects (with `public_url`s). If a project already exists, skip to step 4.

**3. Create and configure the project.**

```bash
curl -s -X POST https://sokuho.emilkarl.dev/api/v1/projects \
  -H "Authorization: Bearer $SOKUHO_TOKEN" -H "Content-Type: application/json" \
  -d '{"project": {"name": "Aboard", "subdomain": "aboard", "website_url": "https://www.aboardhr.com", "accent_color": "#4F46E5", "terminology": "Updates", "voice_prompt": "Friendly, concise, no exclamation marks. We write for HR admins, not developers."}}'
```

Subdomains: lowercase letters, numbers, dashes. `terminology` is what posts are called on the page (Updates, Changelog, News, Releases). Ask your human about voice before writing posts — or propose a `voice_prompt` from their existing writing.

**4. Write a draft.**

```bash
curl -s -X POST https://sokuho.emilkarl.dev/api/v1/projects/$PROJECT_ID/posts \
  -H "Authorization: Bearer $SOKUHO_TOKEN" -H "Content-Type: application/json" \
  -d '{"post": {"title": "Import a whole folder of documents 📥", "body": "200 payslips, one drop...", "category": "Documents", "labels": ["New"]}}'
```

Bodies are markdown (GFM: tables, task lists, strikethrough; raw HTML is stripped). Categories and labels are matched by name and **created automatically** if missing. Emoji in titles are welcome.

**5. Publish (with human approval).**

```bash
curl -s -X POST https://sokuho.emilkarl.dev/api/v1/projects/$PROJECT_ID/posts/$POST_ID/publish \
  -H "Authorization: Bearer $SOKUHO_TOKEN"
```

Then send your human the post's `url` from the response so they can see it live.

## Endpoints

All endpoints require `Authorization: Bearer <token>` and speak JSON. Wrap attributes in the resource key (`{"project": {...}}`, `{"post": {...}}`).

### Me
- `GET /api/v1/me` — user, accounts, projects.

### Projects
- `GET /api/v1/projects`
- `POST /api/v1/projects` — attrs: `name` (required), `subdomain` (required, unique), `custom_domain`, `website_url`, `terminology`, `accent_color` (#rrggbb), `background_color` (#rrggbb), `font_family` (inter, geist, dm-sans, space-grotesk, bricolage-grotesque, ibm-plex-sans, fraunces, source-serif-4, jetbrains-mono), `voice_prompt`, `show_authors`, `hide_from_search_engines`, `private_mode`, `widget_enabled`, `widget_entries_limit` (1–10), `widget_badge_delay_hours` (0–168), `widget_badge_expiry_days` (1–365), `widget_label` (launcher text, max 40), `widget_position` (bottom-right, bottom-left), `github_repository` (`owner/repository`), `github_enabled`, `github_trigger_label`, `github_trigger_marker`. Optional top-level `account_id` when the user belongs to several accounts.
- `GET /api/v1/projects/:id`
- `PATCH /api/v1/projects/:id` — same attrs.
- `DELETE /api/v1/projects/:id` — requires account membership. Destroys all posts; confirm with your human first.

### Posts
- `GET /api/v1/projects/:project_id/posts` — newest first, max 100. Filters: `?status=draft|scheduled|published`. Add `&bodies=true` to include markdown bodies.
- `POST /api/v1/projects/:project_id/posts` — attrs: `title` (required), `body` (markdown), `publish_at` (ISO8601). Top-level-in-post extras: `category` (name, auto-created), `labels` (array of names, auto-created), `publish` (boolean).
    - default → draft
    - `"publish": true` → published now (or at `publish_at` if set — a past `publish_at` backdates)
    - future `publish_at` + `"publish": true` → scheduled; goes live automatically
- `GET /api/v1/projects/:project_id/posts/:id`
- `PATCH /api/v1/projects/:project_id/posts/:id` — same attrs.
- `POST .../posts/:id/publish` · `POST .../posts/:id/unpublish`
- `DELETE .../posts/:id`

### Images
- `POST /api/v1/projects/:project_id/images` — multipart upload. Fields: `image` (file, required; PNG, JPEG, WebP or GIF, max 15 MB), `frame` (`true` to turn a screenshot into a presentation image on the accent color; PNG/JPEG/WebP only), `alt` (alt text). Returns `{"image": {"url", "markdown", "signed_id", "framed", ...}}` — paste `markdown` into a post body.

```bash
curl -s -X POST https://sokuho.emilkarl.dev/api/v1/projects/$PROJECT_ID/images \
  -H "Authorization: Bearer $SOKUHO_TOKEN" -F image=@shot.png -F frame=true -F alt="Bulk upload"
```

### Drafts
- `POST /api/v1/projects/:project_id/drafts` — attrs: `material` (required: PR description, commits or notes), `source_url` (e.g. the PR URL). Same as the `generate_draft` MCP tool: the server writes a draft post in the project's voice and returns `{"post": {...}}`. Synchronous; expect a few seconds. `409` with the existing `post` if one already exists for `source_url`; `503` when AI drafting isn't configured on this server (write the post yourself instead).

### Categories & labels
- `GET/POST /api/v1/projects/:project_id/categories` · `PATCH/DELETE .../categories/:id` — attrs: `name`.
- `GET/POST /api/v1/projects/:project_id/labels` · `PATCH/DELETE .../labels/:id` — attrs: `name`, `color` (#rrggbb).

### Public (no auth)
Each project's page serves machine-readable feeds on its own host:
- `https://<subdomain>.sokuho.emilkarl.dev/feed.json` — published posts as JSON (markdown + rendered HTML).
- `https://<subdomain>.sokuho.emilkarl.dev/feed` — RSS.
- `<script async src="https://sokuho.emilkarl.dev/widget.js" data-project="WIDGET_KEY"></script>` — self-contained in-app updates widget. The `widget_key` is returned with the project API response. The feed is unavailable when the widget is disabled or the project is private.

## CLI

`sokuho` is a single-binary CLI over this API. Install with Go: `go install github.com/emilkarl/lately/cli/cmd/sokuho@latest`.

```bash
sokuho login                      # browser sign-in (OAuth + PKCE), or: sokuho login --token
sokuho projects use <id|subdomain>
sokuho posts create --title "Bulk upload 📥" --body-file notes.md --image shot.png --frame
git log -1 --format=%B | sokuho posts create --title "Fix" --body-file -
sokuho draft --pr 123             # reads the PR with gh, drafts it in the project's voice
sokuho posts publish <id>         # after human approval
```

`--json` prints raw API responses; `--host localhost:3000` targets another server. In CI, set `SOKUHO_TOKEN` (and `SOKUHO_PROJECT`) instead of logging in. The browser login issues an OAuth token with the `mcp` scope, which the REST API accepts the same as a `soku_` token.

## Errors

`401` missing/bad token · `404` resource missing or no access · `422` validation failure (message in `{"error": "..."}`) · `400` malformed request · `409` conflict · `502`/`503` draft generation failed / unavailable.

## Coming soon

- GitHub integration — a `sokuho` label on a merged PR becomes a draft post in your voice.

Questions or a missing endpoint an agent would want? Open an issue: https://github.com/emilkarl/lately
