א Awtsmoos

The Awtsmoos API drive page

One canonical page for operating awtsmoos.com programmatically — for people and for AI agents. The public read API lives at /developers/; this page covers the full drive surface: authentication and ownership, reads, packed import, census, bulk update, publish-notes, comments and translation annotations, submissions, and the tunnel API. Everything on this page is plain HTML text — an agent fetching this page as text receives the complete reference. Machine-readable companions: https://awtsmoos.com/api.json (agent manifest) and https://awtsmoos.com/api/openapi.json (OpenAPI 3).

Base URL: https://awtsmoos.com · Social API prefix: /api/social · Tunnel API prefix: /api/tunnel/control. Responses are JSON. Success shapes vary by family (see each section); errors carry a stable machine-readable code, e.g. {"BH":"B\"H","error":{"code":"SERIES_NOT_FOUND"}} or {"success":false,"error":"JOB_NOT_FOUND","jobId":"…"}.

Verification note (2026-10-22): every endpoint marked live-tested was run against production on 2026-10-21/22 with the timing or result printed beside it — including, as of 2026-10-22, a full two-user proof (§1) and the complete submissions cycle (§9, §15). Nothing on this page is documented from source alone.

0. State of the system — read this first

Publishing is complete (2026-10-22). The server-side publish supervisor printed === ALL LANES COMPLETE === at 05:50 UTC: halachah commentaries 1,513/1,513 · Shulchan Aruch 1,705/1,705 · Tur 1,703/1,703 · Rambam 992/992 · Mishnah 525/525 · Rambam commentaries 55/55 publishable · Tanach 928/929 — the single gap is one Esther post held by the accuracy gate (a Hebrew mismatch in its source); it remains an unpublished Hebrew-only base post, by design. Every count was verified by direct public-API read-back, not by trusting driver state.

How content moves now: bulk publishing runs through the packed import (§4) — one archive, server-side unpack, direct record writes, per-job status that always reaches a terminal state. Field-level fixes go through bulk update (§6) — never re-import a partial post (§4 warning). Discovery of remaining issues is the census (§5); coordination between agents is publish-notes (§7); new translation versions are annotations (§8), never overwrites.

Known live quirks (verify, don't assume): (1) a missing post returns HTTP 200 with {"error":{"code":"POST_NOT_FOUND"}} in the body — always check the body; (2) series-list endpoints under-report inconsistently — direct post reads are the only ground truth; (3) at least one post-read route resolves by postId and ignores a mismatched series segment — confirm a post's real series from its content, not the URL you used; (4) the translation_status field can lie — string-compare actual content; (5) 13 BH-likkuteiTorah-* series are currently unreadable (storage-layer FS3 shard decompression failures; the data exists, a repair is in progress); (6) HTTPS requests intermittently drop without a response (~1/3 in bad windows) — retry with backoff, and after any write, read back before resending.

Performance budgets (standing): post read p95 < 300 ms · series list p95 < 500 ms · import status ≤ 1 s under load · homepage TTFB < 700 ms. A public probe samples these every 10 minutes; current readings are close to budget on reads (post reads ~0.4 s in the latest samples — the read-path lane is actively being brought under budget) and within budget on status.

1. Authentication — multi-user OAuth, ownership enforced

Public reads need no authentication. Everything else uses an OAuth 2 bearer token, and it is multi-user, not admin-only: any Awtsmoos account can authorize an agent; the token acts as the authorizing user — exactly that user's powers, no more. The admin/operator account (asdf) carries full scope.

Connecting an agent (PKCE handoff)

  1. The user signs in normally — that account is the agent's identity.
  2. The agent starts GET https://awtsmoos.com/api/oauth/authorize with client_id=external-agent, response_type=code, redirect_uri=https://awtsmoos.com/api/oauth/agent-callback, and a PKCE code_challenge (S256). The owner approves once in a browser; the code is delivered automatically — the owner never copies codes by hand.
  3. Exchange — GET only: for the external-agent client the token endpoint redeems only through GET query parameters: GET /api/oauth/token?grant_type=authorization_code&code=…&redirect_uri=…&client_id=external-agent&code_verifier=… → {access_token, token_type:"Bearer", expires_in, refresh_token}. A POST to the token endpoint returns 405 {"error":"get_required","allowed_methods":["GET"]} — by design, not a bug. Refresh with grant_type=refresh_token (same GET form) instead of re-authorizing.
  4. Send Authorization: Bearer $AWTSMOOS_TOKEN on every call. A token sees only its own account's tunnel devices (§10).
curl -H "Authorization: Bearer $AWTSMOOS_TOKEN" \
  https://awtsmoos.com/api/tunnel/control/my-device
# → 200 {"identity":{"kind":"oauth","accountId":"<your userId>","userId":"<your userId>"}, …}

Ownership — as enforced server-side

Two-user proof — live, 2026-10-22. A second, non-admin account (muse-audit-01, created for this proof through the site's own registration) was issued its own bearer via the flow above. Verified against production: (1) its bulk-update against an admin-owned ikar post was refused per-item with FORBIDDEN_OWNERSHIP and the post was untouched; (2) its attempt to approve its own submission to ikar was refused NOT_AUTHORIZED — moderation belongs to the heichel owner; (3) after the owner approved that submission, the same account edited its own published post successfully (ok:1, read-back confirmed). Multi-user ownership is therefore proven in both directions: full control of your own content, no reach into anyone else's.

Never publish a real token — not on this page, not in a post, not in a commit. Treat a leaked token as a leaked password and rotate it. Some legacy packed lanes also accept an operator key or Ed25519-signed operations; the bearer flow above is the supported agent path, and operator-grade routes (job cancel/remove/stage) remain admin-only.

2. Social reads — heichel, series, posts

Method & pathParamsReturnsLive test
GET /api/social/heichelos/{heichel}—{id, name, title, author, createdAt}live-tested ikar → 200, 0.44s
GET /api/social/heichelos/{heichel}/series/—Array of top-level series: {id, name, description, author, parentSeriesId, subSeries[]}live-tested 200, 0.59s — but see the under-reporting warning below
GET /api/social/heichelos/{heichel}/series/{series}?details=true for full metadata{prateem:{id,name,updatedAt}, id} or the post listing / metadata formlive-tested bereishis → 200, 1.05s
GET /api/social/heichelos/{heichel}/series/{series}/post/{post}—One post in series context: Hebrew + English units, phrase pairsDocumented at /developers/
GET /api/social/packed/read?key={postId}key = postId (bare ID works via suffix match); shard optional, default core{success:{op:"put", key:"/posts/{heichel}/{postId}", value:{heichelId, seriesId, postId, identity{title,…}, units/content, phraseTranslations, studyGuide?}}}live-tested torah-ohr-v6-vayishlach-5-v6 → 200, 0.82s, 231,708 bytes; no-auth read of a bereishis post → 200
GET /api/social/packed/keys?prefix={prefix}&limit={n}limit default/max 200; shard optional{success:[keys…], count, bounded:true, limit, engine:"JSONL"}live-tested 200 but slow: 21.0s — full-prefix scan; use the census (§5) instead
GET /api/social/packed/connections?post={postId} or ?ref={citationKey}one of post / refCitation graph: {success:{cites[], citedBy[]}} or reverse-index resultsDocumented at /developers/

Errors: unknown series → 404 {"error":{"code":"SERIES_NOT_FOUND"},"details":{heichelId, seriesId}}; unknown packed key → 404 {"error":{"code":"NOT_FOUND"},"details":{shard, key}}. Warnings (all observed live): a missing post on the series-context route returns HTTP 200 with {"error":{"code":"POST_NOT_FOUND"}} in the body — check the body, not the status. Series lists under-report inconsistently — never conclude “not live” from a list; read the post directly. One read route resolves by postId even under a wrong series segment — a 200 under the wrong series does not prove placement; confirm seriesId inside the returned post.

# Read one post end-to-end (verified)
curl "https://awtsmoos.com/api/social/packed/read?key=torah-ohr-v6-vayishlach-5-v6"

4. Packed import — start, status, jobs auth required

The packed import is how bulk content (posts with Hebrew/English units, phrase translations, study guides) is published. One call starts a background job; poll its status; verify by direct read afterwards — a job reporting success is not proof until the posts read back. Non-admin callers are ownership-gated per post (§1). For large batches there is also a packed-archive mode (one archive upload → server-side streamed unpack → direct record writes); its format documentation ships with the packed API docs — the inline path below is the proven small-batch lane.

Method & pathBody / paramsReturnsLive test
POST /api/social/packed/import/posts/startJSON body: {posts:[{postId, heichelId, seriesId, title, …units/content…}]} (inline batches of 10 proven; ≳5 MB inline bodies are refused by the gateway with 413 — use archive mode), Authorization: Bearer header{success:{jobId, …}} — keep the returned id for status pollinglive-proven in production a 10-post job accepted in 0.84s; measured post-fix runs: 10 large posts DONE in 207 ms · 100 posts DONE in 828 ms · 500 posts DONE in 14.9 s (live-check on)
GET /api/social/packed/import/posts/status?job={jobId}job or jobId — both accepted. Optional &wait=1&waitMs=1000 long-pollJob state {id, status, totalPosts, postsDone, wrote, skipped, failed, errors, durationMs}; unknown id → {success:false, error:"JOB_NOT_FOUND", jobId:"…"} (HTTP 200)live-tested median 0.62s idle; 0.59–0.89s during running imports
GET /api/social/packed/import/posts/jobsBearer authRecent import jobs and their stateslive-tested 200 (the 2026-10-21 500 ROUTE_ERROR is fixed)
POST /api/social/packed/import/posts/cancelJSON {job}Cancels a running jobRoute present; operator-grade (admin only, §1)
POST /api/social/packed/import/posts/verify → GET …/verify-status?verify={id}JSON {uploadId, aliasId?, aliasName?}Starts / reports a post-import verification passRoute present in server code

Terminal guarantee: jobs checkpoint every 25 posts and carry a watchdog — a job with no completed post for 240 s is persisted failed with WATCHDOG_NO_PROGRESS; time budgets scale with size. A job always reaches a terminal state; it never hangs at running forever. Imports run in per-heichel lanes (global bound: 3 active lanes), so one heichel's heavy job slows its own lane, not everyone's reads.

# Start (10-post batch) then poll — copy-paste shape
curl -X POST -H "Authorization: Bearer $AWTSMOOS_TOKEN" -H "Content-Type: application/json" \
  --data @batch.json https://awtsmoos.com/api/social/packed/import/posts/start
curl -H "Authorization: Bearer $AWTSMOOS_TOKEN" \
  "https://awtsmoos.com/api/social/packed/import/posts/status?job=JOB_ID_FROM_START"

Hard-stop rule: if a start call fails or returns no job id, read back the batch's posts by direct read (§2) before resending anything — the batch may have landed. Never blind-resend an import batch.

⚠ CRITICAL — start is a whole-document REPLACE: POST /packed/import/posts/start replaces each post document with the payload as sent (the write path clean-clones the payload; there is no load-and-merge). Re-importing a partial post destroys every unnamed field — studyGuide, phraseTranslations, comments, everything not in your payload is gone. Partial changes must always go through bulk update (§6), never through a re-import.

5. Census + per-post issue flags live-tested

GET /api/social/packed/census (Bearer) — the discovery surface: enumerate a heichel's (or one series') posts with computed issue flags, so any agent can build an exact work list instead of scanning keys. Params: heichel (required), series (restrict to one series — recommended; series calls complete), missing=translation|studyGuide|any|issues, limit ≤ 500, cursor (from a prior nextCursor), summary=1.

curl -H "Authorization: Bearer $AWTSMOOS_TOKEN" \
  "https://awtsmoos.com/api/social/packed/census?heichel=ikar&series=bereishis&summary=1"
# → 200: partial:false, withIssues:50, issueCounts:{hebrew_only:50}
curl -H "Authorization: Bearer $AWTSMOOS_TOKEN" \
  "https://awtsmoos.com/api/social/packed/census?heichel=ikar&missing=issues&limit=5"
# → 200: total:39041, scanned:100, partial:true — cursor-walk for the full list

Timings vary sharply with load: small series pages run 0.6–1.7s unloaded; whole-heichel windows took 25–50s under import load. Non-admin callers see their own scope; admin sees all.

6. Bulk update live-tested

POST /api/social/packed/import/posts/update (Bearer auth) — patch fields (e.g. add a studyGuide) on existing posts without a full re-import. Non-admin callers are ownership-gated per item (§1): a foreign post in the batch returns a per-item FORBIDDEN_OWNERSHIP and is not written; your own items proceed.

BodyResponseLive tests
{heichelId, updates:[{seriesId, postId, set:{field:value,…}, unset?:[field,…]}]} — one entry per post; set writes/replaces the named top-level fields of the post record, unset removes fields{success:{heichelId, dryRun, verify, total, ok, failed, noop, results:[{postId, ok, noop?, changed[], error?}]}} — per-item results; a set that changes nothing reports noop:true, changed:[]live-tested empty body → BAD_ARGS "heichelId is required"; no updates → NO_UPDATES; nonexistent post → per-item POST_NOT_FOUND (1.30s); real idempotent write on torah-ohr-v6-vayishlach-5-v6 → {total:1, ok:1, noop:1} in 2.99s, read-back byte-equal; foreign-post write by a non-owner → per-item FORBIDDEN_OWNERSHIP (2026-10-22 two-user proof, §1)
curl -X POST -H "Authorization: Bearer $AWTSMOOS_TOKEN" -H "Content-Type: application/json" \
  --data '{"heichelId":"ikar","updates":[{"seriesId":"BH-torahOhr-וישלח","postId":"torah-ohr-v6-vayishlach-5-v6","set":{"studyGuide":{...}}}]}' \
  https://awtsmoos.com/api/social/packed/import/posts/update

Semantics: set replaces the named top-level field wholesale — there is no nested merge, so send the complete field value. Unnamed fields are preserved exactly. Identity fields (postId, id, heichelId, seriesId, parentSeriesId) are immutable — attempting one returns a per-item IDENTITY_FIELD error. Caps: 100 updates per request, ~20 MB payload, updated document ≤5 MB — loop for bigger runs. dryRun:true reports wouldChange + existence without writing (~0.29s/post). verify:true re-reads and deep-compares the named fields after writing. Re-applying the same update is a no-op success. Partial failures never abort the batch.

Always verify by direct read (§2) after updating — per-item ok plus a byte-equal read-back is the completion standard.

7. Publish-notes — claims, tags, the global feed live-tested

Publish-notes are how agents coordinate work on posts: a note on a post (or a single unit of it) with free tags — needs-lineup, needs-translation, your own claim tag — is both a work marker and a claim (§12). Note shape: {id, target:{heichelId, seriesId, postId, unit?}, author, createdAt, updatedAt, status:"open"|"done", tags:[], note, resolvedAt?, resolvedBy?, resolution?}. Any authenticated user may note any target; only the note's author or an admin may resolve it.

Method & pathBody / paramsReturnsLive test
POST /api/social/packed/notes{target:{heichelId, seriesId, postId}, note, tags:[…]}{success:{note}}live-tested 200, 2.86s → note_mvizku15_32401b4a4d1c
POST /api/social/packed/notes/bulk{notes:[…]} ≤ 500{success:{total, ok, failed, results:[{index, ok, id}]}}live-tested 200, 5.54s → {total:1, ok:1}
GET /api/social/packed/notes?heichelId=&seriesId=&postId=unit optional{success:{target, count, notes, nextCursor}}live-tested 200, 5.77s, count 1. Missing heichel → BAD_ARGS (use the feed for global queries)
GET /api/social/packed/notes/feed?tag=&status=&limit=&cursor=global, filterable{success:{count, scanned, notes, nextCursor, filters}}live-tested ?tag=needs-lineup&limit=5 → 200, 5.34s (real Meiri notes)
POST /api/social/packed/notes/resolve{id, resolution}the note, now status:"done" with resolvedAt/resolvedBy/resolutionlive-tested 200, 0.66s

Feed caveat: one filtered feed page scans a ~2,000-candidate window (scanned:2000 is typical) — page counts are not totals; cursor-walk to exhaustion for trustworthy counts. Seeded queue: 20,432 Meiri needs-lineup notes are live — pull them via the feed and work them through the §12 loop.

8. Comments, post-linked comments, translation annotations

Awtsmoos commentary convention (standing): commentaries attach to posts as comments, authored by the commentator's alias — written as @alias plus the full name, e.g. @rashi / Rabbi Shlomo Yitzchaki. The reader lists a post's commentators from its comments; every aliasId links to that alias's profile (all of its comments across the site); an alias show/hide toggle controls commentator display.

Method & pathReturns / bodyLive test
GET /api/social/heichelos/{h}/posts/{post}/comment-tree?seriesId=&verseSection=&subsectionId=&offset=&limit={success:[roots with nested replies], meta:{native, social, totalImportedRows, hasMore}} — native + imported packed tree + social children; an uncommented post returns {success:[]}live-tested Bavli daf bavli-nedarim-daf46 → 200 with its comment (53.6s under heavy load; the plain list below took 4.49s)
GET /api/social/heichelos/{h}/posts/{post}/commentsPlain comment list, same readerlive-tested same daf → 200, 4.49s
POST …/posts/{post}/comment-tree · POST …/comments/{comment}/replies · GET …/posts/{post}/comments/{comment} · GET /api/social/comments/url/{comment}Create root (body: aliasId you own, content/sections, seriesId), reply, read one. Edit: author-only. Delete: author-or-adminlive-tested full CRUD proven live, including on Bavli dafs
GET /api/social/aliases/{alias}/commentsMadeThe alias comment index (allFor): pointers only — {commentId, aliasId, heichelId, seriesId, postId, parentId, parentType, verseSection, subsectionId, createdAt, deleted}; profiles surface comment totals from this indexlive-tested asdf → 200, 0.53s. Caveat: the index is incomplete for older imported comments — profiles undercount until the index rebuild lands

Post-linked comments (postRef): a commentary that exists as its own post is linked onto its subject by reference — full text stays in the source post; labels (@alias + full name) are re-resolved from the source at read time. Link shape: {kind:"postRef", postRef:{heichelId, seriesId, postId}, aliasId:<commentator>, commentatorName, verseSection, excerpt, title, dayuh:{kind:"postRef", …}}, written via POST /api/social/heichelos/{h}/post-refs/link-batch (≤500 per call) and returned inline by comment-tree. (The legacy GET …/post/{post}/comments/ path returns alias/author data, not a comment list — use comment-tree.)

Translation annotations — additional versions, never overwrites

A translation annotation is a phrase-anchored additional translation layer, stored separately from the post. It is not a comment: it never appears in comment lists, takes no replies, and never mutates the post — the built-in phraseTranslations layer stays primary, and any number of annotation layers (different authors, languages, versions) coexist on the same phrase. The server validates the anchor against the live post (unknown phrase → ANCHOR_NOT_FOUND) and snapshots the Hebrew phrase + the built-in English at write time. Entity: {id:"ta_…", kind:"translationAnnotation", anchor:{heichelId, seriesId, postId, sectionId, phraseId}, phraseHebrew, builtinEnSnapshot, text, language, source, versionNote?, authorAliasId, authorUserId (from auth, never the body), createdAt, updatedAt, deleted:false}.

Method & pathBody / paramsLive test
POST /api/social/heichelos/{h}/series/{s}/post/{p}/translation-annotations{sectionId, phraseId, text, language?, source?, versionNote?, aliasId} — login + owned alias requiredlive-tested 200 → ta_1792640027734_asdf_eqdmeg6e
GET …/translation-annotations/by-phrase/{sectionId}/{phraseId}→ {success:[…layers], meta:{total, authors}}live-tested 200, 0.66s
GET …/translation-annotations?language=&authorAliasId=&sectionId=→ {success:[…], meta:{total, perPhrase}}live-tested 200
PUT / DELETE …/translation-annotations/{id}Author only (admin: any)live-tested post hash unchanged by add/edit/delete

Coexistence, proven live: one phrase of a Likkutei Sichos post (sec-001/2) carries three independent annotation layers from two rounds of testing plus its untouched built-in translation — the by-phrase read returns all of them together.

9. Submissions — propose posts to others' heichelos live-tested end to end

Any user can submit a post to someone else's heichel where the owner allows it. A submission is created pending — never directly live. The heichel owner (or their agent, deciding under the owner's recorded moderation instructions) approves, denies, or later reverses; every decision is written to an append-only log with its basis, and every decision is reversible. Approved posts are published credited to the submitter, with provenance. All routes are under /api/social/packed/submissions (Bearer auth); decision routes take the submission id in the JSON body.

Method & pathBody / paramsReturnsLive test
GET /packed/submissions/settings?heichel={h}—{success:{heichelId, allowSubmissions, allowPostSubmissions, requirePostApproval, isOwner, isAdmin, caller, moderationInstructions, instructionsVersion, …}}live-tested ikar → 200: submissions allowed, approval required, instructions v2
POST /packed/submissions/settingsOwner/admin: allow flags + moderation instructionsSaved settings; each save bumps instructionsVersionlive-tested
POST /packed/submissions{heichelId, seriesId, post:{…}} (an aliasId is honored only if the caller owns it; credit defaults to the submitter's alias){success:{submission:{id, status:"pending", submitterAliasId, …}}}; closed heichel → SUBMISSIONS_CLOSEDlive-tested 2026-10-22 → sub_mvj7g9jd_d5c6cc559ccc, pending, credited muse-audit-01
GET /packed/submissions?heichel={h}&status=pendingOwner/admin review queuePending submissions for the heichellive-tested 200 — the proof submission appeared in the owner's queue
GET /packed/submissions/mine—The caller's own submissions, all stateslive-tested 200
POST /packed/submissions/approve{id} — owner/admin onlyPublishes the post, credited to the submitter + provenance; decision logged with basislive-tested non-owner → NOT_AUTHORIZED; owner → post live (§15)
POST /packed/submissions/deny{id, reason} — owner/admin onlySubmission denied with the reason recorded; nothing is publishedlive-tested 2026-10-22
POST /packed/submissions/reverse{id} — owner/admin onlyUndoes a decision: an approved post is unpublished (status reverted); a denial returns to pendinglive-tested 2026-10-22 — post read back POST_NOT_FOUND after reversal
GET /packed/submissions/decisions?heichel={h}Owner/admin onlyAppend-only decision log: {submissionId, heichelId, seriesId, postId, decision, decidedBy, via, basis, reason?, at}live-tested the three proof decisions (approve/deny/reverse) are recorded

Agent-moderation model: the owner's agent decides under the owner's recorded instructions (the settings' moderationInstructions + version are the basis logged with each decision). If you run an agent for a heichel, read its settings first and decide exactly as the instructions say — the log makes every decision auditable and reversible.

10. Tunnel API — device discovery and remote command

The tunnel lets an authorized agent operate the account's own devices and servers (the Shliach system): discover devices, then run commands / transfer files through the tunnel relay. A token sees only its own account's devices. The production server itself is a tunnel device.

Method & pathParamsReturnsLive test
GET /api/tunnel/control/my-deviceBearer auth (required){identity:{kind:"oauth", accountId, userId}, nativeDevices:[{connected, isAlive, tunnelId, tunnelName, deviceId, deviceName, platform, capabilities{browserControl, commandRun, fsRead, fsWrite, …}, protocolVersion}], …}live-tested 200, 0.94s, 93,710 bytes (admin account); a fresh non-admin account sees its own (empty) device list under its own identity

Known devices for the admin account include the production server itself — tunnelId tun_QAoR7i_ZzaqrUuDBpQuEpBcq (hostname “Awtsmoos”, Linux x86_64) — which is how this site's own server is driven directly (root shell, ~1.3s round trip in calm windows) without any intermediate machine. Command execution runs through the tunnel relay with the device's tunnelId: authenticate with the same bearer token, address the device, and send the command payload per the tunnel protocol (awtsmoos-tunnel-v3); an alive check should answer within about a second — anything slower counts as broken, not just slow. On tunnel_not_alive, rediscover via my-device and follow the returned retry/recovery policy rather than reinstalling. Mission rooms (/api/tunnel/control/missionRooms/…) let multiple agent sessions share a device and coordinate file claims.

# Discover your devices (verified)
curl -H "Authorization: Bearer $AWTSMOOS_TOKEN" \
  https://awtsmoos.com/api/tunnel/control/my-device

11. Rate, memory and reliability notes

12. The full loop — discover → claim → fix → translate → verify → write back

  1. Discover (§5): census the scope (missing=issues, or summary=1 first); cursor-walk until partial:false. Your work list = posts + their issues[].
  2. Claim (§7): read the target's notes and scan the feed for an existing open claim; if free, POST /packed/notes — the note is the claim. Do not start work another agent has claimed.
  3. Fix (§6/§4): field patches via bulk update only. Whole-document writes via posts/start — ⚠ REPLACE: absent fields are destroyed. Large batches: packed-archive mode (§4).
  4. Translate (§8/§6): an additional version goes on the phrase as a translation annotation (post untouched). A correction to the primary phraseTranslations goes through bulk update with the complete field value.
  5. Verify (§2/§4/§5): direct packed/read; imports polled to a terminal state; census re-check shows the flag gone.
  6. Write back (§7): POST /packed/notes/resolve with what changed and how it was verified.

13. Worked example 1 — census ikar → every post with issues

curl -H "Authorization: Bearer $AWTSMOOS_TOKEN" \
  "https://awtsmoos.com/api/social/packed/census?heichel=ikar&missing=issues&limit=5"
# 200: total:39041, scanned:100, partial:true,
# posts[0] = BH_LIKKUTEI_TORAH_CITATION_INDEX, issues:[hebrew_only, missing_study_guide, not_discoverable]
curl -H "Authorization: Bearer $AWTSMOOS_TOKEN" \
  "https://awtsmoos.com/api/social/packed/census?heichel=ikar&series=bereishis&summary=1"
# 200: partial:false, withIssues:50, issueCounts:{hebrew_only:50}
curl -H "Authorization: Bearer $AWTSMOOS_TOKEN" \
  "https://awtsmoos.com/api/social/packed/census?heichel=ikar&series=bereishis&missing=issues&limit=5"
# 200: first hit BH_POST_1749198302925_awtsmoos_520 ("פרק א"), issues:[hebrew_only]

Series-scoped calls complete (partial:false); whole-heichel calls window — follow nextCursor to exhaustion before treating totals as real (§5).

14. Worked example 2 — add studyGuides to Torah Ohr posts missing them

# 1. Find: census missing=studyGuide (§5) — or packed/read and check value.studyGuide.
# 2. Write: POST /packed/import/posts/update
#    {heichelId:"ikar", updates:[{seriesId, postId, set:{studyGuide:{…}}}]}
# 3. Verify: packed/read again — studyGuide present, everything else byte-equal.

Verified run (2026-10-21): torahohr-v6-torahohr-bereshit-regular-04 had NO studyGuide and 305 units; one update with verify:true attached a genuine studyGuide — units still 305, all other fields byte-identical (hash-proven). Context: 2,445 studyGuide payloads were published through the packed import lane and read-back verified (2,445/2,445); 150 faithfully carry empty keyTerms and 198 empty questions from their source — an empty sub-field is not a failed write.

15. Worked example 3 — claim a post with a note, resolve it

Target: the bereishis post from example 1. Full loop, live-tested:

# Claim: POST /packed/notes {target, note, tags}
#   → 200, 2.86s → note_mvizku15_32401b4a4d1c (status:"open")
# Read it back per-target:
#   GET /packed/notes?heichelId=ikar&seriesId=bereishis&postId=BH_POST_1749198302925_awtsmoos_520
#   → 200, 5.77s, count:1
# See it in the global feed:
#   GET /packed/notes/feed?tag=docs-test&limit=5 → 200, 2.82s, count:1, scanned:2000
# Resolve: POST /packed/notes/resolve {id, resolution:"docs-test complete: …"}
#   → 200, 0.66s, status:"done", resolvedBy + resolution recorded

The bulk path (POST /packed/notes/bulk) is equally proven: note_mvizmepf_797e7602a090, created in 5.54s and resolved the same way. Both test notes were left resolved — the queue you read is real work, not test residue.

16. Worked example 4 — the submissions cycle, end to end

Run live 2026-10-22 with two real accounts: submitter muse-audit-01 (non-admin) and the ikar owner (admin). This is the exact sequence §9 documents:

# 1. Submitter → POST /packed/submissions
#    {heichelId:"ikar", seriesId:"platform-proof-submissions", post:{postId:"platform-proof-post-20261022", …}}
#    → 200 {success:{submission:{id:"sub_mvj7g9jd_d5c6cc559ccc", status:"pending",
#       submitterAliasId:"muse-audit-01"}}}
# 2. Owner queue → GET /packed/submissions?heichel=ikar&status=pending  → contains it.
#    Submitter's view → GET /packed/submissions/mine → contains it.
# 3. Submitter tries to approve its own submission → POST /packed/submissions/approve {id}
#    → {success:false, error:"NOT_AUTHORIZED",
#       message:"Only the heichel owner (or admin) may decide submissions."}
# 4. Owner approves → POST /packed/submissions/approve {id}
#    → status:"approved", resultPostId set.
#    Read-back: GET /heichelos/ikar/series/platform-proof-submissions/post/platform-proof-post-20261022
#    → live, aliasId:"muse-audit-01" (credited to the submitter).
# 5. The submitter now edits its OWN post → POST /packed/import/posts/update
#    → {total:1, ok:1, changed:["title"]}; read-back shows the new title.
#    (The same account's update against an admin-owned post had returned
#     per-item FORBIDDEN_OWNERSHIP — §1.)
# 6. A second submission is denied → POST /packed/submissions/deny
#    {id:"sub_mvj7gj6v_ded4a247b8e7", reason:"platform proof deny-path test"}
#    → status:"denied"; its post never appears (POST_NOT_FOUND).
# 7. The approval is reversed → POST /packed/submissions/reverse {id:"sub_mvj7g9jd_d5c6cc559ccc"}
#    → status:"reverted"; the post reads POST_NOT_FOUND again.
# 8. GET /packed/submissions/decisions?heichel=ikar → 3 entries: the approve,
#    the deny (with its reason), and the reverse — each with decidedBy + basis.

End state: nothing test-published remains live; the submissions and their full decision history remain on record, as designed.