Instructions to Agents — API Access & Site Administration
Audience: a non-human agent (LLM agent, CI job, monitoring bot, script) that needs to
read, manage, or administer this site programmatically. This document is complete and
self-contained: together with the site's base URL — and, for admin work, a bearer
token issued by a human administrator — nothing else is required.
1. What this site is
The site is a content platform organised as a three-level hierarchy:
- View — a top-level publication (its own title, branding, and sections).
- Section — an ordered group of items inside a view.
- Item — a single piece of content: metadata (a JSON "sidecar") plus a body document (markdown or HTML).
Everything below is relative to BASE_URL — the origin this page was served from
(e.g. https://example-site.example.com).
There are two API surfaces:
| Surface | Prefix | Authentication |
|---|---|---|
| Public read API | /api/* |
None — reflects exactly what anonymous visitors see |
| Admin API | /admin/api/* |
Bearer token (agents) or browser session (humans) |
2. Public API — no authentication required
All public endpoints are plain HTTPS requests with JSON responses unless noted.
Use them to verify the public surface ("is the site up", "is the new item visible").
| Operation | Endpoint |
|---|---|
| Site registry (all views + default view id) | GET /api/views |
| One view (title, description, sections) | GET /api/views/:viewId |
| Items of a section (published only) | GET /api/views/:viewId/sections/:sectionId/items |
| One item's metadata | GET /api/views/:viewId/items/:slug |
| One item's rendered body | GET /api/views/:viewId/items/:slug/body |
| Download an item's source document | GET /api/views/:viewId/items/:slug/download |
| Search within a view | GET /api/views/:viewId/search?q=<query> |
| Submit a content suggestion (challenge-protected) | POST /api/suggestions |
| Mailing-list subscribe / confirm | POST /api/subscribe, POST /api/subscribe/confirm |
| Mailing-list unsubscribe / confirm | POST /api/unsubscribe, POST /api/unsubscribe/confirm |
Start any session with GET /api/views: it returns the registry of view ids you will
need for every other call.
Notes:
POST /api/suggestionsis the only anonymous write. It is rate-limited and protected
by an ALTCHA proof-of-work challenge; suggestions are stored privately for human
editors and are never published automatically. Prefer the/suggestweb form; only
automate this endpoint if you have been instructed to.- Subscribe/unsubscribe endpoints exist only when the mailing subsystem is enabled on
this deployment; otherwise they answer404/503.
3. Getting admin access
Admin access is granted only by a human administrator. There is no self-service
registration, and agents can never mint credentials for themselves or other agents.
- A human administrator signs in to the admin UI and opens
/admin/agents
("Agent tokens"), or runs the operator CLI (npm run admin:agent:create). - They issue a credential with an agent id, a label, and an expiration
(30/90/365 days, a custom date, or never-expires). - The plaintext bearer token is shown exactly once. The administrator hands it to you
through a secure channel, together with the site'sBASE_URL.
The token is an opaque string of the form:
<agentId>.<secret>
The part before the first dot is your public agent id (e.g. monitor-bot); the rest is
a secret. The server stores only an HMAC hash of the token — if you lose it, it cannot
be recovered, only re-issued.
Your token carries the admin:* scope: every admin API operation is available to you
EXCEPT token management (§6).
4. How to authenticate
Send the token in the Authorization header of every request:
Authorization: Bearer <agentId>.<secret>
Rules:
- HTTPS only. Never call the API over plain HTTP.
- Header only. Never place the token in a URL, query string, request body, or log
line — URLs leak into server logs and referrers. - Store it in a secret manager. Never write the token into files, code, tickets,
chat, or your own output. When reporting about yourself, identify by your publicagentIdonly. - No CSRF token needed. CSRF protection applies to human browser sessions only;
bearer-token requests are exempt. Do not fetch/admin/api/csrf; do not sendX-CSRF-Tokenor_csrf. - No cookies needed. Do not maintain a session; every request is independently
authenticated by the header. An invalid bearer token fails with401— it does not
fall back to a session cookie.
Verify your access on handoff with a harmless read:
curl -sS -H "Authorization: Bearer $TOKEN" "$BASE_URL/admin/api/views"
# expect: HTTP 200 with the site registry JSON
5. Admin API — full endpoint inventory
Mutations take JSON bodies (Content-Type: application/json) unless noted. The public
catalog regenerates synchronously after every successful write — your change is
publicly visible on the next request; there is no cache to wait for.
Reads (monitoring surface)
| Operation | Endpoint |
|---|---|
| Site registry (all views, including drafts) | GET /admin/api/views |
| Sections of a view | GET /admin/api/views/:viewId/sections |
| Items in a section (all statuses) | GET /admin/api/views/:viewId/sections/:sectionId/items |
| Suggestions queue | GET /admin/api/suggestions |
| Mailing-list subscribers (read-only) | GET /admin/api/subscribers |
Writes (management surface)
| Operation | Endpoint |
|---|---|
| Create view | POST /admin/api/views |
| Update view | PATCH /admin/api/views/:viewId |
| Delete view | DELETE /admin/api/views/:viewId |
| Create section | POST /admin/api/views/:viewId/sections |
| Update section | PATCH /admin/api/views/:viewId/sections/:sectionId |
| Reorder sections | POST /admin/api/views/:viewId/sections/reorder |
| Delete section | DELETE /admin/api/views/:viewId/sections/:sectionId |
| Create item (multipart: sidecar JSON + body file) | POST /admin/api/views/:viewId/sections/:sectionId/items |
| Update item metadata | PATCH /admin/api/views/:viewId/sections/:sectionId/items/:slug |
| Replace item body | PUT /admin/api/views/:viewId/sections/:sectionId/items/:slug/body |
| Archive item (soft removal) | POST /admin/api/views/:viewId/sections/:sectionId/items/:slug/archive |
| Restore archived item | POST /admin/api/views/:viewId/sections/:sectionId/items/:slug/restore |
| Move item between sections | POST /admin/api/views/:viewId/sections/:sectionId/items/:slug/move |
| Delete item | DELETE /admin/api/views/:viewId/sections/:sectionId/items/:slug |
| Update a suggestion (approve/reject/promote) | PATCH /admin/api/suggestions/:id |
| Gemini search re-index | POST /admin/api/gemini/resync (only when semantic search is enabled; otherwise 503) |
Worked example — creating a view:
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data '{
"version": 1,
"id": "agent-created-view",
"title": "Agent Created View",
"description": "Created through agent admin authentication.",
"source": { "backend": "azure-blob", "root": "agent-created-view" },
"sections": [],
"status": "draft"
}' \
"$BASE_URL/admin/api/views"
Item creation is multipart/form-data with two parts: a sidecar part containing the
item's metadata JSON and a file part containing the body document. All other mutations
are plain JSON. You can discover most request shapes from reads: fetch an existing
record and mirror its structure. For write operations whose shape you cannot discover
from reads, ask your operator for the administrator operations guide.
Safety rules for destructive operations
- Deletes are permanent.
DELETEon a view or section removes it AND all its
contents. Never delete anything you were not explicitly instructed to delete;
prefer archiving (status: "archived"/ thearchiveendpoint — soft removal from
the public site) over deletion. - Read before you write. Fetch the current record, modify the fields you intend to
change, and send only those fields in the PATCH — do not reconstruct records from
memory. - One mutation at a time. The store uses optimistic concurrency; a
409means
someone else (the human admin, another agent) changed state under you — re-read and
retry ONCE, then stop and report if it persists. - Never retry non-2xx blindly. Retry-storms against
401/403responses look
like an attack and achieve nothing.
6. What you can NOT do — token management is human-only
The token-management endpoints reject every agent token with 403, regardless of scope:
GET /admin/api/agents(list credentials)POST /admin/api/agents(issue a credential)POST /admin/api/agents/:agentId/revoke(revoke a credential)
This is deliberate and permanent: agents cannot mint, enumerate, or revoke agent
credentials. Issuance belongs to the human administrator. Do not attempt these calls
except at most once during handoff verification to confirm the 403 boundary; repeated
attempts will be treated as misbehavior.
You also cannot manage human admin accounts or sessions — /admin/api/auth/* is for
human sign-in flows only.
7. Error semantics — what each status means for you
| Status | Meaning | What you must do |
|---|---|---|
401 UNAUTHORIZED |
Your token is invalid, expired, or revoked. The server deliberately does not tell you which. | Stop all work immediately. Do not retry. Report to your operator that your credential stopped working, quoting your agentId (never the secret). Resume only when handed a new token. |
403 FORBIDDEN |
You are authenticated but this operation is not allowed for agents (§6 endpoints). | Do not retry. This boundary is by design; report if the operation was part of your instructions. |
400 VALIDATION_ERROR |
Your request body/params are malformed. | Fix the request; do not resubmit the identical payload. |
404 NOT_FOUND |
The view/section/item/suggestion does not exist (or was deleted concurrently). | Re-read the parent listing before deciding anything. |
409 CONFLICT |
Duplicate id, or a concurrent write beat yours. | Re-read, retry once; then stop and report. |
413 PAYLOAD_TOO_LARGE |
Upload exceeds the configured size cap. | Do not chunk-and-hammer; report. |
503 |
A subsystem is not configured on this deployment. | The capability is absent — report, don't retry. |
5xx (other) |
Server fault. | Back off (≥ 30 s), retry at most twice, then report. |
8. Token lifecycle — expiry and revocation
- Your token MAY have an expiration date. You cannot query it (the credential listing
is human-only): treat any401as the end of your credential's life, whatever the
cause. - Revocation is immediate: the administrator can revoke your token at any moment, and
your next request will get401. - On
401: stop, report, wait. Never attempt to work around a dead credential —
do not probe other endpoints, do not guess tokens, do not fall back to public
endpoints to simulate writes. - Expect rotation: your operator may hand you a replacement token and revoke the old
one at any time. Always use the most recently issued token; discard superseded ones
from your secret store.
9. Handoff checklist (run once when you receive a token)
GET $BASE_URL/admin/api/viewswith theAuthorizationheader → expect200.
Your credential works.GET $BASE_URL/api/viewswithout any header → expect200. The public surface is
reachable.- Optionally
GET $BASE_URL/admin/api/agentswith the header → expect403.
Confirms the human-only boundary; do not repeat it. - Store the token in your secret manager, delete it from anywhere else it transited,
and begin your instructed duties.
How to retrieve these instructions
This document is published by the site itself, so any agent can (re-)obtain it at any
time without authentication:
Machine-readable (recommended for agents):
GET $BASE_URL/instructions-to-agents.md
returns this exact document as plain markdown (text/markdown), with no site chrome.curl -sS "$BASE_URL/instructions-to-agents.md"Human-readable:
GET $BASE_URL/instructions-to-agentsserves the same content as
a styled web page.
If you are an agent and were given only the site's base URL, fetch the markdown
endpoint above and follow this document from the top. Re-fetch it at the start of each
work session — the API surface and the rules in this document may evolve, and the
published version is always authoritative.