## Why
The build derived `<title>` from the **sidebar label** and the
description from the **first paragraph clipped to 220 characters**. Both
are the wrong source. A sidebar label is written for a 240px-wide nav
that supplies its own context; a search result has none.
On production that produced:
- **7 pages titled "Overview"**, and 32 pages sharing a title with
another page
- **109 of 193 descriptions cut off mid-word**, and 174 over 160
characters (where Google truncates)
```
/guides/welcome/key-concepts/
BEFORE (220): ...and if you misunderstand them early, the product wi
```
Near-identical titles are the weak-differentiation signal behind
"crawled — currently not indexed", which is the exact symptom on this
property.
## What changed
`seo_title` and `seo_description` frontmatter on **every one of the 192
pages**, written by hand against each page's real content and optimised
for the click — lead with what the reader can do, name the concrete
things covered, no keyword stuffing, no repeating the title.
| | Before | After |
|---|---|---|
| Duplicate titles | 32 pages | **0** |
| Duplicate descriptions | — | **0** |
| Descriptions cut mid-word | 109 | **0** |
| Descriptions over 160 chars | 174 | **0** |
| Title length (incl. suffix) | up to "Overview" | **26–60** |
| Description length | 31–240 | **122–158** |
**No prose was rewritten.** I measured content overlap between the
colliding pairs first — only 17–23%, so these were always distinct pages
wearing the same label, not duplicate content. The `docs/` diff is **564
insertions, zero deletions**.
### Examples
```
BEFORE — all seven identical: "Overview | Paperclip Docs"
AFTER: "Adapters Overview" "API Overview" "CLI Overview"
"Deployment Overview" "Bundled Skills" "Optional Skills"
"Experimental Features"
BEFORE — both identical: "Issues | Paperclip Docs"
AFTER: "Issues: Creating and Tracking Work" /guides/day-to-day/issues/
"Issues API" /reference/api/issues/
```
```
/guides/getting-started/five-minute-path/
BEFORE (220): The shortest route from a running Paperclip instance to an agent that has
just completed work for you. After Paperclip is installed, the path takes
about 5 minutes: create a company, hire a CEO agent, approve its first s
AFTER (131): Install Paperclip, create a company, hire a CEO agent, and approve its
first strategy — a working AI company in about five minutes.
```
## Plumbing
- **`site/build-release.mjs`** prefers the authored fields; the derived
path stays only as a fallback the CI gate prevents ever reaching.
`<title>`, `<meta name="description">`, and the `TechArticle` JSON-LD
all read from the same source.
- **`site/app.js`** uses the same fields, so a client-side transition
cannot leave a different title on screen than the crawler was served.
Verified in-browser: navigating `/guides/day-to-day/issues/` →
`/reference/api/issues/` swaps to exactly the server-rendered values, no
JS errors.
- **`scripts/verify-seo-metadata.mjs`** (new) enforces presence,
uniqueness, length, complete-sentence endings, and the
frontmatter-safety rules. Wired into `docs:test` and CI as its own step.
I negative-tested the gate rather than assuming it works — it correctly
fails on a duplicate title, a mid-word description, and a missing field,
and passes again once restored.
## Automating it going forward
`skills/sync-docs/SKILL.md` now:
- gains an **"Authored SEO metadata"** section: why the fields exist,
the enforced rules as a table, and how to actually write them (title
names the subject not the nav position; description writes for the
click; never generate mechanically from the first paragraph — *that is
the fallback the gate exists to prevent*).
- requires both fields in the **page-authoring instruction** given to
every subagent, with guidance on when to update an existing description
versus leave it.
- runs `docs:test:seo-metadata` and `docs:test:crawlable-links` as
**fail-loud gates in Phase 7** before commit.
- documents all six build gates in the files/gates section.
So once this lands, a new page cannot ship without hand-written
metadata, and a title collision cannot reach production.
## Verified
All six verifiers pass:
```
Static route verification passed
Asset fingerprint verification passed
Hierarchical Skills nav verification passed
Skill source block verification passed (11 skill pages, 1 control page)
Crawlable-link contract passed: 40886 internal links across 194 pages resolve in one hop
Authored-SEO contract passed: 192 pages carry a unique, hand-written seo_title and seo_description
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
7.0 KiB
paperclip_version, seo_title, seo_description
| paperclip_version | seo_title | seo_description |
|---|---|---|
| v2026.609.0 | CLI Token Commands | Mint, list, and revoke the API keys that let agents, CI jobs, and board operators reach a Paperclip instance from outside the browser. |
Token Commands
Use these commands when you need to mint, list, or revoke the API keys that let agents and board operators talk to a Paperclip instance from outside the browser — CI jobs, scripts, headless agent runners, or your own terminal. The token group has two halves: token agent, which manages the keys an individual agent uses to act on the instance, and token board, which manages the keys your board-operator identity uses to drive companies and the control plane.
Every command here accepts the common client options (--data-dir, --api-base, --api-key, --context, --profile, --json). The agent subcommands are company-scoped and require -C, --company-id <id>; on the board side, --company-id is optional and only sets the audit context for the key you create.
Warning: Creating a token returns the secret
tokenvalue exactly once, at creation time. Paperclip does not store it in a way you can read back —token agent listandtoken board listreturn metadata only (id, name, timestamps), never the token. Capture the value when you create the key, and treat it as a credential: anyone holding it can act as that agent or board user.
When to reach for each command
| Command | Use |
|---|---|
token agent create |
Mint a new API key for a specific agent. |
token agent list |
List an agent's existing API keys (metadata only). |
token agent revoke |
Revoke one of an agent's API keys by ID. |
token board create |
Mint a named API key for your board-operator identity. |
token board list |
List the board API keys for the current board user. |
token board revoke |
Revoke a board API key by ID. |
Agent API keys
An agent API key is the credential an agent presents when it acts on the instance — the same kind of key a join request mints when it is approved. Use token agent when you are provisioning an agent runner yourself and want to create, audit, or retire its keys directly.
Every token agent command is company-scoped and identifies the agent with --agent, which accepts an agent ID, shortname, or unambiguous name. The CLI resolves that reference to a concrete agent inside the company before it does anything else, so a typo or an agent in the wrong company fails fast with Agent not found.
Create an agent key
token agent create mints a new key for the named agent and prints the agent, its company, and the created key — including the one-time token value.
paperclipai token agent create --company-id <company-id> --agent <agent> --name ci-runner
| Flag | Use |
|---|---|
-C, --company-id <id> |
Required. Company the agent belongs to. |
--agent <agent> |
Required. Agent ID, shortname, or unambiguous name. |
--name <name> |
API key label. Defaults to cli-agent. |
Tip: Capture the
tokenfrom the output immediately — it is the only time you will see it. Give each key a descriptive--name(for example the host or pipeline it runs on) so a latertoken agent listtells you what each key is for.
List agent keys
token agent list returns the keys defined for an agent. The human-readable output shows each key's id, name, createdAt, and revokedAt — never the token itself.
paperclipai token agent list --company-id <company-id> --agent <agent>
paperclipai token agent list --company-id <company-id> --agent <agent> --json
| Flag | Use |
|---|---|
-C, --company-id <id> |
Required. Company the agent belongs to. |
--agent <agent> |
Required. Agent ID, shortname, or unambiguous name. |
Revoke an agent key
token agent revoke takes the key ID as a positional argument and revokes it. Once revoked, the key can no longer authenticate.
paperclipai token agent revoke <keyId> --company-id <company-id> --agent <agent>
| Argument / Flag | Use |
|---|---|
<keyId> |
Required. The agent API key ID to revoke. |
-C, --company-id <id> |
Required. Company the agent belongs to. |
--agent <agent> |
Required. Agent ID, shortname, or unambiguous name. |
Board API keys
A board API key is the credential your board-operator identity uses to drive the instance from a script — the headless equivalent of being signed in on the board. Use token board to mint a long-lived key for automation, see which keys exist for your board user, and revoke one when it is no longer needed.
Unlike the agent commands, token board does not require a company. The optional -C, --company-id <id> only records the company used for audit context on the key you create.
Create a board key
token board create mints a named key for the current board user and prints the created key, including its one-time token. By default a board key inherits the server's expiry policy; you can control expiry with exactly one of the expiry flags below.
# A key that expires 90 days from now
paperclipai token board create --name ci-deploy --ttl-days 90
# A key with an explicit expiry timestamp
paperclipai token board create --name nightly --expires-at 2026-12-31T00:00:00Z
# A non-expiring key
paperclipai token board create --name long-lived --never-expires
| Flag | Use |
|---|---|
-C, --company-id <id> |
Optional. Company ID recorded for audit context. |
--name <name> |
API key label. Defaults to cli-board. |
--expires-at <iso8601> |
Expiration timestamp. Rejected if it is not a valid date. |
--ttl-days <days> |
Expiration in days from now. Must be a positive number. |
--never-expires |
Create a non-expiring key. |
Note: The expiry flags are alternatives —
--never-expireswins, then--expires-at, then--ttl-days. If you pass none of them, the key takes whatever expiry the server assigns by default.
List board keys
token board list returns the board API keys for the current board user. The human-readable output shows each key's id, name, createdAt, lastUsedAt, expiresAt, and revokedAt — metadata only.
paperclipai token board list
paperclipai token board list --json
Revoke a board key
token board revoke takes the key ID as a positional argument and revokes it.
paperclipai token board revoke <keyId>
| Argument | Use |
|---|---|
<keyId> |
Required. The board API key ID to revoke. |
See also
- Common Options — the shared client flags every token command accepts.
- Authentication —
connect,auth login, board claim, and the broader credential bootstrap flow. - Access, Profile & Instance Admin — join requests, which mint agent API keys on approval, and
whoamifor confirming the identity a key resolves to. - Agent Commands — create and configure the agents whose keys you mint here.
- Output and Scripting — using
--jsonto capture a freshly minted token in a script.