# MusePort API

Revision `2026.10.09.001`. Native WordPress REST namespace: `museport/v1`.

The base is `https://YOUR-SITE/wp-json/museport/v1`. Use the exact HTTPS destination and refuse redirects when attaching credentials. No key belongs in a URL, chat message, source repository or public documentation.

| Route | Credential and purpose |
|---|---|
| `POST /events` | `X-MusePort-Key`, only submits for the key's agent |
| `GET /events?page=1&per_page=50&agent_id=1` | WordPress administrator capability, private paginated activity |
| `GET /runs?page=1&per_page=50&agent_id=1` | WordPress administrator capability, private latest task-run states |
| `GET /status` | WordPress administrator capability, counts and receipt time |
| `GET /agents` | WordPress administrator capability, registry with no keys or hashes |
| `POST /agents` | Secure native administrator session plus `X-WP-Nonce`; `{slug,name}`, one-time writer key |
| `POST /agents/ID/rotate` | Same native administrator session; invalidates earlier key and returns one replacement |
| `POST /agents/ID/revoke` | Same native administrator session; disables writes and preserves prior activity |

Writer keys have exactly `events:write`; they cannot read even their own feed, change membership, call admin routes, send messages or authorize WordPress operations. A writer header is rejected on every read/admin route, including a request which also carries an admin cookie. Name and identity come from the key's registry record, never an activity payload.

## Minimal activity

```json
{
  "request_id": "report-week-20261009-state-1",
  "run_id": "report-week-20261009-attempt-1",
  "kind": "task",
  "state": "running",
  "summary": "Preparing a report from verified source numbers.",
  "occurred_at": "2026-10-09T12:00:00Z",
  "outcome": "none",
  "delivery": "not_applicable",
  "proof": {"kind": "none"}
}
```

This is illustrative input, not a seeded report or proof of execution. UTC times must be real, no more than five minutes in the future and within the 90-day retention period.

`kind`: `task`, `mail_receipt`, `heartbeat`. `state`: `queued`, `running`, `waiting`, `succeeded`, `failed`, `blocked`, `cancelled`, `observed`. Tasks need `run_id`; their outcome is `none` and delivery `not_applicable`. Use a separate run ID for each task attempt. Out-of-order task observations do not overwrite a newer reported state.

Heartbeats use `observed`, no run ID, `none` outcome and `not_applicable` delivery. They update contact time without creating or completing a task.

Mail receipts use `observed` with the exact `accepted`, `partial`, `rejected` or `uncertain` outcome. SMTP acceptance is not delivery, so normally use `unconfirmed`. Reported `confirmed` delivery requires `accepted` plus a `provider_receipt` proof. This is still an agent-supplied external claim, never independent MusePort confirmation.

Proof accepts `kind` (`none`, `worker_result`, `provider_receipt`, `manual_check`), a short opaque `reference` and `observed_at`. Any proof other than `none` requires both reference and UTC time. `none` has neither. References are identifiers, never paths, URLs, email addresses or credentials.

Request, run and proof identifiers use only letters, numbers, `.`, `_`, `:` and `-`, beginning with a letter or number. Limits are 120, 80 and 120 characters respectively. Summaries are at most 280 Unicode characters. JSON is at most 4,096 bytes. Unknown fields such as `body`, `password`, `agent_id` or artifact paths fail validation. Do not upload source records and rely on redaction to make them safe.

## Replay, limits and truth

The unique pair is authenticated agent plus `request_id`. Retry an uncertain HTTP submission with identical minimized content and the same request ID. Identical replay returns the original `event_id` and `replayed:true`; conflicting content returns HTTP 409. Mail-provider uncertainty never authorizes another email submission.

Successful ingestion returns `event_id`, server `received_at` in UTC, `agent_id` and `replayed`. It proves that MusePort received and saved the minimized activity. Agent `occurred_at`, state and proof remain `agent_reported`. An HTTP receipt does not establish that the reported task or external delivery succeeded.

Limits: 60 new events per agent per minute, 100 registered identities, 10,000 retained events, 90 days. Identical replay does not count as a new event. HTTP 429 means retry the same event after waiting; HTTP 409 may mean request conflict or capacity reached; HTTP 400 is invalid metadata; 401 means missing, rotated or revoked writer credential; 403 means wrong role or insecure transport. Do not expose technical exceptions or raw database errors to users.

## Optional existing MusePost receipt adapter

Keep the existing email client unchanged. Obtain one typed receipt with its existing JSON interface under the worker's authorized private account configuration. The observer adapter accepts an already exported `schema_version:1` receipt, not a full mail record. It copies actual SMTP outcome, hashes private request/Message-ID references and discards account names, recipients, full warnings and original private paths.

```sh
python3 worker/receipt_adapter.py --receipt-file /private/receipt.json
```

Default execution is offline and only prints the minimized event. It never invokes MusePost or contacts a mail server. Explicit `--publish --config /private/museport.json` posts exactly that event to the selected plugin using an independently issued writer credential. Config is owner-only mode 0600 and contains only `base_url` and `key`. The adapter refuses credential-bearing redirects, uses a 15-second timeout and preserves retry identity. The supplied sample file is documentation, not a live connection.

Neither the generic observer nor its optional adapter establishes that Taylor/Muse or any other existing agent is connected. Verify an actual receipt from the intended worker before claiming integration.

## Generic task and heartbeat sender

`worker/event_sender.py` supports separately authorized workers which report real task observations or contact heartbeats. Python 3's standard library is enough. It validates the same typed metadata and redacts common contact, credential, URL and private-path patterns before submission. It does not run a command, start an agent, obtain credentials, read the activity feed or contact a mail provider.

```sh
python3 worker/event_sender.py --event-file /private/actual-observation.json
python3 worker/event_sender.py --event-file /private/actual-observation.json \
  --publish --config /private/museport.json
```

The first command is offline. Only explicit `--publish` contacts the exact configured HTTPS API base. `--event-file -` reads stdin without putting metadata or credentials in process arguments. Credentials are loaded only from an owner-only mode-0600 config, never supplied on the command line or embedded in event JSON. Keep `event_sender.py` and `receipt_adapter.py` together on the worker.

Prepare each observation only after the reported action actually occurs. Use its real source time, a stable request ID for that state observation and an opaque run ID for the task attempt. Report `succeeded` only after the worker's actual action passes its required checks; attach an existing opaque evidence reference and its real observation time when available. These fields remain the worker's claims. Server `received_at` proves storage, not task completion or external delivery.

Persist or retain the exact minimized event while its HTTP outcome is uncertain. Retry identical content with the same request ID; an existing row returns `replayed:true`. HTTP 409 does not generate a new ID or retry automatically: inspect the original request and reconcile a conflict or capacity limit. HTTP 429 requires waiting while keeping the event ID. No sender retry creates another task, sends another message or changes any external outcome.

A deliberate administrator-issued Codex writer can report actual release checks through this interface. That connection proves only the events received from that specific worker. It does not establish that Taylor/Muse or other existing agents are connected.
