diff --git a/.claude/skills/production-scan/SKILL.md b/.claude/skills/production-scan/SKILL.md
index 39915be..2203483 100644
--- a/.claude/skills/production-scan/SKILL.md
+++ b/.claude/skills/production-scan/SKILL.md
@@ -589,20 +589,42 @@ re-scans don't clobber):
| `verdictHandle` / `quip` *(optional)* | `verdict_handle` / `verdict_quip` | stored verbatim by the API (no validator rule; keep ≤500 as skill-side discipline) — lets the hosted page adopt the scan's own voice (renderer support tracked website-side) |
```bash
+ # First upload of an id — no auth. The 201 body includes a ONE-TIME `writeToken`;
+ # save it (rule below) — overwriting this id later requires it.
curl -X POST /api/reports -H "Content-Type: application/json" -d @.production-scan/api-payload.json
+ # Re-submitting an id we already claimed (same-day re-scan) — send the saved token:
+ curl -X POST /api/reports -H "Content-Type: application/json" \
+ -H "Authorization: Bearer " -d @.production-scan/api-payload.json
```
+ **Write token (authorship binding, API side 2026-07-03):** the first POST for an id
+ *claims* it — the 201 body carries a one-time `writeToken`, and every later POST of
+ the **same id** must send it as `Authorization: Bearer ` or the API
+ answers 401 (so strangers can't rewrite a hosted report they didn't publish).
+ Handling is mandatory:
+ - **Before POSTing**, look the id up in `.production-scan/write-tokens.json`; if a
+ token exists, attach the `Authorization` header.
+ - **On 201**, if the body contains `writeToken`, merge `{"": ""}` into
+ `.production-scan/write-tokens.json` (create the file if missing). The token is never
+ shown again; losing it means this id can no longer be overwritten (recovery: `-2`).
+
**Submission semantics — one payload, at most 3 delivery attempts:**
- Retry (backoff 1s/2s/4s, 10s timeout each) ONLY on network error / timeout / 5xx.
- **429** → stop immediately (Retry-After can be up to an hour); fall back per Step 9.5.
- **400** → the payload violates the table above; fix the payload — the corrected body
is a NEW submission, never re-send an identical body. Reserved-id 400 → append `-2`
to the id and resubmit once.
+ - **401** → the id is claimed and we don't hold its token (write-tokens.json lost, or
+ someone else published under this id first): append `-2` to the id — a fresh claim,
+ same recovery as the reserved-id 400 — and resubmit once. Never re-send an
+ identical 401 body.
- **413** → drop `markdown` from the payload and resubmit once.
On 201, use the returned URL; anything else → the Step 9.5 fallback. The per-IP budget
is 20 POSTs/hour — one scan = one logical submission.
**`.production-scan/` must be gitignored by default** (add it to `.gitignore`); it
-carries bounded evidence only — **never raw secret values or full file contents**.
+carries bounded evidence only — **never raw secret values or full file contents** —
+plus `write-tokens.json`, the per-id overwrite credentials for the hosted reports
+(one more reason the directory stays out of git).
The user opts in to committing a report. Keeping timestamped JSON lets a later scan
diff against the previous one ("fixed 2, 1 new since "). The fix flow and
re-scan read **only the JSON** — Markdown is a human-only leaf.
@@ -1272,7 +1294,10 @@ shareable without manual deployment.
composed id — the date suffix must survive); if the result starts with a
non-alphanumeric, prefix `r`; if it equals a reserved sample id, append `-2`
- The date suffix separates scans from different days; **a same-day re-scan of the
- same repo reuses the id and OVERWRITES the hosted report (latest wins — deliberate)**
+ same repo reuses the id and OVERWRITES the hosted report (latest wins — deliberate)**,
+ authenticated by the id's `writeToken` from `.production-scan/write-tokens.json`
+ (Step 7 write-token rule); without it the API answers 401 and the re-scan
+ publishes under `-2`
- Reports are stored **permanently** (product decision 2026-07-02) — share links never expire
- **How the URL is produced (MANDATORY production flow with SAFETY checks):**
1. After building `scan-report.json`, determine the base URL using the detection logic above
@@ -1332,7 +1357,9 @@ shareable without manual deployment.
- This is THE conversion point - never skip
✅ **HTML Report URL (Step 9.5)**
-- POST once (≤3 delivery attempts); 201 → hosted URL is the last line
+- POST once (≤3 delivery attempts); 201 → hosted URL is the last line; persist any
+ returned `writeToken` to `.production-scan/write-tokens.json` and send it as a
+ Bearer header when re-POSTing the same id (Step 7 write-token rule)
- Any failure → local-fallback line + cause (honest URL rule; no dead links)
- Auto-detect base (First Tree → production, FIRST_TREE_BASE_URL, else localhost)
- Production: `https://first-tree.ai/production-scan/r/--`