Tashi zuwa abunna
Fitinty

Fitinty API reference

Generated from the API the server is running right now, filtered to the doors a key may open. It is never written by hand.

Keys and subscriptions are minted from a business's Setup desk: Connected apps > API & webhooks.

3,280 doors across 21 families a key may open, and 200 public doors that need no key.

How a key is presented

A key is minted from a business's Setup desk (Connected apps > API & webhooks) by its admin, shown once, stored as a hash, and revoked by a row. It acts as a member of exactly that business: it reads and writes what a member of that business could, in the families its scopes name, and nothing of any other.

Authorization: Bearer fk_<key>

education:read · education:write · marketplace:read · marketplace:write · publishers:read · publishers:write · workspace:read · workspace:write · services:read · services:write · ministries:read · ministries:write · media:read · media:write · cinema:read · cinema:write · games:read · games:write · marketing:read · marketing:write · tools:read · tools:write · offices:read · offices:write · bank:read · bank:write · treasury:read · treasury:write · tax:read · tax:write · compliance:read · compliance:write · investors:read · investors:write · farms:read · farms:write · manufacturing:read · manufacturing:write · logistics:read · logistics:write · governor-suite:read · governor-suite:write

Webhooks

A subscription names an https URL and families of the audit trail. Each event of those families is delivered once, signed; a receiver that does not answer 2xx is retried on the backoff and the delivery is marked dead after the last try, with the reason. The payload is what the trail records about the event - never the row it is about; read that through the API with a key.

Signature

X-Fitinty-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 over '<t>.' + the exact body bytes, under the subscription's secret>
X-Fitinty-Event: <action>
X-Fitinty-Delivery: <delivery id>
tolerance: 300s

Retries (seconds between attempts)

60, 300, 1800, 7200, 21600 - 6 attempts

Families a subscription may name

assignment, automation, bank, cinema, compliance, course, deck_factory, education, enrollment, farms, games, governor, identity_pack, imperial_draw, investors, lesson, logistics, manufacturing, marketing, marketplace, media, ministries, multimedia_factory, office, offices, payment, presentation_factory, provider, publishers, school, song_factory, tax, theme, tools, treasury, video_factory, voice, workspace

id, occurred_at, family, action, resource_type, resource_id, organization_id, actor_id, decision, delivery_id, attempt

Families a key may open

Education - 291 doors - education:read, education:write

Schools, programmes and courses - the framework every tenant's school is built from.

  • DELETE /education/announcements/{announcement_id} · scope education:writeDelete Announcement
  • POST /education/announcements/{announcement_id}/publish · scope education:writePublish AnnouncementTHE OVERRIDE DOOR. Whatever drafted it - a hand or the digest - a person publishes. RLS admits only school staff to the UPDATE.
  • POST /education/assignments/{assignment_id}/submissions · scope education:writeSubmit Assignment
  • GET /education/assignments/{assignment_id}/submissions · scope education:readList Assignment Submissions
  • POST /education/assignments/{assignment_id}/writing-feedback · scope education:writeWriting Feedback
  • POST /education/blocks · scope education:writeCreate Block
  • PATCH /education/blocks/{block_id} · scope education:writeUpdate Block
  • DELETE /education/blocks/{block_id} · scope education:writeDelete Block
  • GET /education/bundles/for-course/{course_id} · scope education:readBundles For CourseThe student's door: published bundles that carry this course as an item.
  • GET /education/bundles/mine · scope education:readMy BundlesThe buyer's shelf: every bundle they bought, with each item's delivery status IN WORDS - a pending or failed delivery is shown, never hidden.
  • POST /education/bundles/purchases/{purchase_id}/retry-delivery · scope education:writeRetry DeliveryThe retry door: the buyer or the selling school's staff re-run whatever is not yet delivered. Delivered rows are never re-attempted.
  • POST /education/bundles/schools/{school_id} · scope education:writeCreate BundleThe seller's hand. Items are validated against REAL rows at creation - a key with no reader is a silent zero, so an unknown reference is refused (422), never stored. Born a draft; publishing is the go-live moment.
  • GET /education/bundles/schools/{school_id} · scope education:readList School BundlesRLS splits honestly: staff see drafts, everyone else the published shop window.
  • POST /education/bundles/{bundle_id}/checkout · scope education:writeStart Bundle CheckoutOne Checkout Session on the SELLER's account for the whole box. When any item is physical, Stripe collects the shipping address and the webhook carries it into the Medusa order - the buyer types their address exactly once.
  • POST /education/bundles/{bundle_id}/publish · scope education:writePublish Bundle
  • GET /education/certificates/mine · scope education:readList My Certificates
  • POST /education/certificates/{certificate_id}/revoke · scope education:writeRevoke Certificate
  • GET /education/certificates/{certificate_id}/verify · scope education:readVerify Certificate
  • DELETE /education/channel-messages/{message_id} · scope education:writeRemove Channel MessageModeration (addition 18's seed): RLS admits the DELETE only for the author, school staff, or the owner - zero rows deleted is an honest 404.
  • GET /education/commerce/applications/mine · scope education:readMy Applications
  • POST /education/commerce/applications/{application_id}/decide · scope education:writeDecide ApplicationA decline carries its reason - the reason is the applicant's teacher.
  • POST /education/commerce/courses/{course_id}/book · scope education:writeSet Course BookThe course's book dial. The book must be a PUBLISHED product somewhere on the platform - any org's: a partner bookstore counts.
  • POST /education/commerce/courses/{course_id}/checkout · scope education:writeStart Course CheckoutA Checkout Session ON THE SCHOOL'S ACCOUNT. No card detail touches this platform - Stripe hosts the page; the student pays the school; Fitinty's fee is taken by Stripe's own split.
  • POST /education/commerce/courses/{course_id}/license-seats · scope education:writeLicense SeatsTHE BUYER'S DOOR. Another organization's OWNER buys seats on the seller's own Stripe account - a Checkout Session, the course-purchase construction exactly. No row is granted here: the verified webhook births the seats, so an abandoned checkout leaves no free students behind.
  • POST /education/commerce/courses/{course_id}/seat-offer · scope education:writeSet Seat OfferThe seller's shelf tag. Only on a PUBLISHED course - the pay-to-open gate was passed when the course went public, and seats on a draft would sell an empty room.
  • POST /education/commerce/courses/{course_id}/sponsor-checkout · scope education:writeStart Sponsor CheckoutTHE GIVER'S DOOR. Any signed-in person pays the course's own price on the school's own rail. The claim code is minted NOW and shown on the sponsor's shelf the moment payment lands - handing it over is the gift.
  • POST /education/commerce/courses/{course_id}/template-license · scope education:writeBuy Template LicenseTHE BUYER'S DOOR. A priced template is the seat-purchase construction exactly: pending row, Checkout Session on the seller's account, the webhook births the license. A FREE template births its license paid on the spot - there is no provider payment to wait for.
  • POST /education/commerce/courses/{course_id}/template-offer · scope education:writeSet Template OfferThe seller's dial. Published courses only - a template nobody can inspect as a live course would sell a promise with no shop window. Going ON SALE is a go-live moment, so it rides the ONE pay-to-open gate like every other.
  • GET /education/commerce/courses/{course_id}/template-offer · scope education:readGet Template OfferThe seller's own dial, read back - staff only, drafts and inactive included.
  • POST /education/commerce/enrollments/{enrollment_id}/certificate-checkout · scope education:writeStart Certificate CheckoutThe STUDENT's door - their own enrollment, their own money, the school's own rail.
  • POST /education/commerce/programs/{program_id}/checkout · scope education:writeStart Program CheckoutBuy the whole series at the tenant's one price. Published programs only - a bundle of drafts would sell an empty shelf.
  • POST /education/commerce/schools/{school_id}/application-fee · scope education:writeSet Application FeeThe school's dial. None = free applications (the default, and the honest zero).
  • GET /education/commerce/schools/{school_id}/applications · scope education:readSchool Applications
  • POST /education/commerce/schools/{school_id}/apply · scope education:writeApply To SchoolThe applicant's door. Free -> SUBMITTED now; priced -> PENDING_PAYMENT with the school's own checkout - the verified webhook turns it submitted.
  • POST /education/commerce/schools/{school_id}/growth-advice · scope education:writeGrowth AdviceProposals, never actions (Override Charter): grounded strictly in the school's own real numbers, benchmarked in spirit against the plan's competitive rider - and everything it suggests is applied by the TENANT's own hand through the existing screens or not at all.
  • GET /education/commerce/schools/{school_id}/money-in · scope education:readSchool Money InThe school's money-in: PAID purchases only (the money-in rule: captured, never intended). Absence of payments is said in words by the screen, never rendered as zero-pretending-to-know.
  • GET /education/commerce/schools/{school_id}/seat-grants · scope education:readList Seat Grants
  • POST /education/commerce/schools/{school_id}/seat-grants · scope education:writeCreate Seat Grant
  • GET /education/commerce/seat-purchases · scope education:readList Seat PurchasesBoth sides of the counter: what this org has sold and what it has bought.
  • POST /education/commerce/sponsored/claim · scope education:writeClaim SponsorshipThe student's hand on the gift. One claim, forever; a code on an unpaid or already-claimed gift refuses in words. The claim opens the PRICE gate only - the student still walks enrollment's other gates on the course page.
  • GET /education/commerce/sponsored/mine · scope education:readMy SponsorshipsThe sponsor's shelf: every gift they bought, its state, and the claim code to hand over once paid.
  • GET /education/commerce/template-licenses/mine · scope education:readMy Template LicensesThe buyer's shelf: licenses their organization holds, adopted or waiting.
  • POST /education/commerce/template-licenses/{license_id}/adopt · scope education:writeAdopt TemplateDELIVERY. The paid license turns into a DRAFT course in the buyer's school - the whole spine cloned, owned outright, free to reshape (building is free by ruling; the buyer's own publish walks the standing publish gates later). What is NOT copied is stated in words, never silently absent.
  • GET /education/commerce/template-market · scope education:readTemplate MarketEvery active offer on a published course, platform-wide. Read through the PUBLIC cursor: these are shop-window facts, and a member cursor answers a silent zero on another org's rows - the standing RLS lesson. Free is stated as free, never 0.
  • POST /education/commerce/webhook · scope education:writeEducation Stripe Webhook
  • PATCH /education/competencies/{competency_id} · scope education:writeUpdate CompetencyCorrect a competency's wording. A competency statement is the hardest sentence in a course to get right - it is the promise the whole module is measured against - so it is exactly the sentence a teacher rewrites three times. Shipping create-only was the same capability-without-a-door fault found five times on the night this pillar was walked (Tenant Zero, 2026-08-26). THE LINKS SURVIVE THE REWOR…
  • DELETE /education/competencies/{competency_id} · scope education:writeDelete CompetencyRemove a competency - unless a student has already been credited with it. A demonstration is a statement about a person's ability, granted by a named member of staff with evidence attached. Deleting the competency underneath it would erase that record sideways, which is the thing this platform refuses everywhere: a teacher's tidy-up never rewrites a student's history.
  • POST /education/competencies/{competency_id}/assessed · scope education:writeMark AssessedClaim that an assessment TESTS a competency. This is the claim the coverage report checks.
  • POST /education/competencies/{competency_id}/demonstrated · scope education:writeRecord DemonstrationTHE ALTERNATIVE PATH: this student can already do this, and here is how we know. In job training this is the ordinary case rather than the exception - a technician who has wired houses for ten years has mastered "identify an electrical hazard", and a school that cannot record that wastes their time and loses them. Recognition of prior learning, kept as a record with a name and evidence on it, nev…
  • POST /education/competencies/{competency_id}/taught · scope education:writeMark TaughtDeclare WHERE a competency is taught. Exactly one anchor - the table's own CHECK says the same thing, so a caller that bypasses this endpoint cannot write a nonsense row either.
  • GET /education/courses/published · scope education:readList Published Courses
  • GET /education/courses/{course_id} · scope education:readGet Course
  • PATCH /education/courses/{course_id} · scope education:writeUpdate Course
  • GET /education/courses/{course_id}/assignments · scope education:readList Assignments
  • POST /education/courses/{course_id}/assignments · scope education:writeCreate Assignment
  • GET /education/courses/{course_id}/attendance · scope education:readList Attendance
  • POST /education/courses/{course_id}/attendance · scope education:writeRecord Attendance
  • GET /education/courses/{course_id}/certificates · scope education:readList Course Certificates
  • GET /education/courses/{course_id}/channel · scope education:readGet Channel
  • POST /education/courses/{course_id}/channel · scope education:writePost To Channel
  • GET /education/courses/{course_id}/competencies · scope education:readList CompetenciesWhat a learner will be able to DO. Visible to students too, deliberately: telling somebody the target is the point of stating it.
  • POST /education/courses/{course_id}/competencies · scope education:writeCreate Competency
  • GET /education/courses/{course_id}/coverage · scope education:readCourse CoverageDoes the course's final lab, project and exam test everything it teaches?
  • POST /education/courses/{course_id}/enroll · scope education:writeEnroll
  • GET /education/courses/{course_id}/enrollments · scope education:readList Course Roster
  • GET /education/courses/{course_id}/grades · scope education:readAll Grades
  • GET /education/courses/{course_id}/grades/mine · scope education:readMy Grade
  • GET /education/courses/{course_id}/grading-weights · scope education:readGet Grading Weights
  • PUT /education/courses/{course_id}/grading-weights · scope education:writeSet Grading Weights
  • GET /education/courses/{course_id}/labs · scope education:readCourse LabsOne call for the whole course player: which lessons carry a lab, and which of them THIS caller has genuinely completed. Rows exist only where a premise does - a lesson with no lab is absent, not false-flagged.
  • GET /education/courses/{course_id}/live-classes · scope education:readList Live Classes
  • POST /education/courses/{course_id}/live-classes · scope education:writeStart Live ClassStaff only. A REAL P01 hosted meeting through create_meeting() itself - its capacity checks, its room tokens, its /meet room - linked to the course.
  • GET /education/courses/{course_id}/modality-coverage · scope education:readCourse Modality Coverage
  • GET /education/courses/{course_id}/module-progress · scope education:readGet Module Progress
  • GET /education/courses/{course_id}/modules · scope education:readList Modules
  • POST /education/courses/{course_id}/modules · scope education:writeCreate Module
  • GET /education/courses/{course_id}/prerequisites · scope education:readList Prerequisites
  • POST /education/courses/{course_id}/prerequisites · scope education:writeAdd Prerequisite
  • DELETE /education/courses/{course_id}/prerequisites/{requires_course_id} · scope education:writeRemove Prerequisite
  • POST /education/courses/{course_id}/publish-to-federation · scope education:writePublish To Federation
  • GET /education/courses/{course_id}/quizzes · scope education:readList Quizzes
  • POST /education/courses/{course_id}/quizzes · scope education:writeCreate Quiz
  • GET /education/courses/{course_id}/review · scope education:readReview CourseThe whole course. Kept as its own route because it is what every screen already calls.
  • GET /education/courses/{course_id}/spine · scope education:readCourse SpineThe student's own gated view of a course. THE EMPEROR'S SEQUENCE, read from records rather than asserted: "student work on each section at a time until complete the lesson; each lesson at a time until they complete the Module; and each module at a time until complete the course." THIS ENDPOINT DOES NOT INVENT A POLICY. Sequencing follows the course's own `require_sequential_modules` flag — the s…
  • GET /education/courses/{course_id}/students/{student_user_id}/report · scope education:readStudent Report
  • POST /education/courses/{course_id}/study-groups · scope education:writeCreate Study GroupAnyone inside the walls starts a room; staff-started rooms are the school's official ones. The creator is its first member.
  • GET /education/courses/{course_id}/study-groups · scope education:readList Study Groups
  • GET /education/courses/{course_id}/translation-siblings · scope education:readList Translation SiblingsEvery real course in this one's translation family - lets a student/teacher navigate to any language version from any other one. Returns an empty list for a course with no translations at all - never a 404, since 'no family' is a real, valid state, not an error.
  • POST /education/courses/{course_id}/translations · scope education:writeCreate Translation
  • GET /education/courses/{course_id}/translations · scope education:readList Translations
  • GET /education/enrollments/mine · scope education:readList My Enrollments
  • PATCH /education/enrollments/{enrollment_id} · scope education:writeUpdate Enrollment Status
  • POST /education/enrollments/{enrollment_id}/certificate · scope education:writeIssue Certificate
  • PATCH /education/enrollments/{enrollment_id}/term · scope education:writeSet Enrollment TermAttach an enrolment to the term it belongs to - the write migration 397 never got. FOUND BY THE FIRST FULL DRY-RUN (2026-08-25): the term attaches to the ENROLMENT by design, every reader filters on `enrollment.term_id` - and nothing in the product ever wrote it. Every fixture that had a term got it from wave-time SQL, so on a fresh school the whole Registrar was unreachable: every student "unter…
  • PUT /education/factory/categories/{school_type} · scope education:writeSave CategoryAdd a category, or rewrite one. Owner only, and the table says so too. The slug is taken from the URL, not the body: renaming a category's slug would cascade to every school built on it, and that is a different and much larger decision than editing its wording.
  • POST /education/factory/categories/{school_type}/restore · scope education:writeRestore Category
  • POST /education/factory/categories/{school_type}/retire · scope education:writeRetire CategoryWithdraw a category without deleting it. RETIRED, NEVER DELETED, and the database enforces the difference: `school.school_type` is a foreign key with ON DELETE RESTRICT, so a category any school was built on cannot be removed at all. Retiring stops anyone starting a new school of that kind while every existing one keeps a real parent and keeps working.
  • POST /education/factory/lessons/{lesson_id}/multimedia · scope education:writeBridge Lesson To Factory
  • POST /education/factory/proposals · scope education:writeCreate Proposal
  • GET /education/factory/proposals · scope education:readList My Proposals
  • PATCH /education/factory/proposals/{proposal_id} · scope education:writeEdit ProposalThe human-edit door - the Override Charter's first working implementation. The tenant sends the draft as THEY want it; it is validated for shape only, never second- guessed, and the edit marks the proposal human_touched forever.
  • POST /education/factory/proposals/{proposal_id}/apply · scope education:writeApply Proposal
  • POST /education/factory/proposals/{proposal_id}/discard · scope education:writeDiscard Proposal
  • POST /education/factory/proposals/{proposal_id}/revise · scope education:writeRevise ProposalThe in-the-loop step: re-draft WITH the current draft and the tenant's instruction.
  • GET /education/factory/starter-packs · scope education:readList Starter PacksRLS does the gating: tenants receive published packs only; the owner also sees drafts awaiting review and retired incumbents.
  • POST /education/factory/starter-packs/{pack_id}/adopt · scope education:writeAdopt Starter PackThe pack becomes the TENANT'S proposal. RLS on the SELECT is the gate: a tenant can only ever read - and therefore adopt - a published pack.
  • POST /education/factory/starter-packs/{pack_id}/publish · scope education:writePublish Starter Pack
  • POST /education/factory/starter-packs/{school_type}/generate · scope education:writeGenerate Starter Pack
  • GET /education/factory/templates · scope education:readList Category TemplatesEvery category the Factory can build, with the template each drafts from. Auth-gated but not role-gated: the cockpit shows these to any signed-in tenant choosing what kind of school to start, and nothing in a template is secret - it is the platform's own pedagogy, stated.
  • GET /education/federation/catalog · scope education:readFederation CatalogPublished masters discoverable across tenants (the White-Label catalog).
  • POST /education/federation/{federation_id}/adopt · scope education:writeAdopt Federation
  • GET /education/federation/{federation_id}/events · scope education:readFederation Events
  • POST /education/federation/{federation_id}/global-push · scope education:writeGlobal Push
  • GET /education/federation/{federation_id}/subscribers · scope education:readFederation Subscribers
  • POST /education/guardians/consents · scope education:writeGrant Consent
  • POST /education/guardians/consents/{consent_id}/revoke · scope education:writeRevoke Consent
  • POST /education/guardians/links · scope education:writeLink Guardian
  • POST /education/guardians/minor-flags · scope education:writeMark Minor
  • GET /education/guardians/my-students · scope education:readMy StudentsThe guardian portal seed: progress, not just consent. Every query below runs under the guardian's own RLS context - the *_guardian_read policies (387) are what admit each row.
  • GET /education/identity/assets/{asset_id}/file · scope education:readAsset FileServe one identity file to an eye RLS already trusts - the SELECT below runs under the same policies as everything else, so 'not yours to see' and 'does not exist' are the same 404. Used by the cast card (N6): a face is shown only to sessions the ledger's own policy would show the row to.
  • POST /education/identity/avatar/build · scope education:writeBuild AvatarQueue the Foundry (Wave N, N3): the consented photos become a rigged, walking, game-fidelity character. Runs in the background - a body takes a minute or two to build, and the ledger IS the status: the avatar_rig row appears when it is done.
  • GET /education/identity/avatar/status · scope education:readAvatar Status
  • POST /education/identity/consents · scope education:writeGrant ConsentAn ADULT's own-hand consent. A minor is refused here IN WORDS - and would be refused by the INSERT policy even if this check were deleted, because a child self-consenting to biometric capture must be impossible, not merely discouraged.
  • POST /education/identity/consents/{scope}/revoke · scope education:writeRevoke ConsentRevocation with a receipt: the consent closes AND every asset in the scope dies with its file, in the same transaction the revocation commits in. The response says how many - a receipt, not a shrug. (A minor's revocation travels through the guardians module, which calls the same `_withdraw_assets` - one implementation, both doors.)
  • GET /education/identity/mine · scope education:readMy Identity
  • POST /education/identity/photos · scope education:writeUpload PhotoOne whole-body photo (the agent asks for several, from all angles - each arrives here). Requires the 'likeness' consent to already stand, whichever door granted it.
  • POST /education/identity/voice-sample · scope education:writeUpload Voice SampleThe voice sample - the single most personal thing this platform stores (voice_promise's own words), so it lands in the same ledger the withdrawal walk destroys from.
  • GET /education/integrations · scope education:readList IntegrationsHonest status for the LMS adapters this pillar could integrate with - all genuinely "not configured" right now. The spec (§8) explicitly warns against rushing an LMS integration ("Fitinty shall not attempt to replace all LMS engines immediately") and lays out a real pre-install checklist (license review, security review, SSO design, tenant isolation review, backup/restore, accessibility review) be…
  • POST /education/integrations/import · scope education:writeImport Outline
  • POST /education/labs/transcribe · scope education:writeTranscribe SpeechSpeech into the scene (Wave N, N5): the student SPEAKS their answer and the practice dock gets text. Auth-gated pass-through to the Forge STT service - the model is GPU-sized and lives where the GPU lives, exactly like the tutor's brain. A dead bridge answers in words, not a hang: the student can always fall back to typing.
  • DELETE /education/lessons/{lesson_id} · scope education:writeDelete LessonSame rule one level down: a lesson nobody has practised in deletes; one with lab completions or quiz results behind it does not.
  • PATCH /education/lessons/{lesson_id} · scope education:writeUpdate Lesson
  • GET /education/lessons/{lesson_id}/blocks · scope education:readList Lesson Blocks
  • POST /education/lessons/{lesson_id}/compute-sage · scope education:writeCompute Sage In Lesson
  • POST /education/lessons/{lesson_id}/draft-code · scope education:writeDraft Code LessonReal AI-drafted Python/Sage code from a plain-English description (task #159/160), meant for insertion into a real Jupyter cell - the teacher still reviews it and must explicitly click Run before anything executes in the sandbox. Never executed here - text only.
  • POST /education/lessons/{lesson_id}/draft-diagram · scope education:writeDraft Diagram LessonAI-drawn schematic for a lesson (Tenant Zero, 2026-08-26 - the Emperor: a lesson on circuits NEEDS a circuit). The model drafts an SVG figure, it is sanitized to inert shapes-and-text, and it lands in the lesson's EXISTING image slot - the student page already renders that slot, so no new surface. The teacher sees it immediately and can replace or delete it like any uploaded image.
  • POST /education/lessons/{lesson_id}/draft-latex · scope education:writeDraft Latex LessonReal AI-drafted LaTeX from a plain-English description (task #159/160) - Education's real analog of Publishers' draft_latex. The teacher still edits it live in MathLive and must explicitly save the lesson - this endpoint only ever returns text.
  • POST /education/lessons/{lesson_id}/image · scope education:writeUpload Lesson Image
  • GET /education/lessons/{lesson_id}/image · scope education:readGet Lesson Image
  • DELETE /education/lessons/{lesson_id}/image · scope education:writeDelete Lesson Image
  • POST /education/lessons/{lesson_id}/jupyter/execute · scope education:writeExecute Jupyter Cell Lesson
  • POST /education/lessons/{lesson_id}/jupyter/start · scope education:writeStart Jupyter Session Lesson
  • POST /education/lessons/{lesson_id}/jupyter/stop · scope education:writeStop Jupyter Session Lesson
  • GET /education/lessons/{lesson_id}/lab-completion · scope education:readGet Lab CompletionThe caller's own real completion record for this lesson's VR Lab Console (id 76's mandatory interactive-practice requirement) - None if they haven't finished it (or haven't started), never fabricated.
  • POST /education/lessons/{lesson_id}/lab-completion · scope education:writeRecord Lab CompletionCalled once the client has confirmed every interactive object in the lesson's scene has genuinely been inspected - a real per-student record, not a client-only toy. RLS (lesson_lab_completion_write) independently re-enforces active enrollment; this endpoint doesn't duplicate that check, it relies on the database to reject an ineligible write.
  • GET /education/lessons/{lesson_id}/lab-environment · scope education:readGet Lab EnvironmentServes the lab's panorama. RLS on the lesson_scenario SELECT already scopes visibility (published, or staff of the lesson's school) - a row coming back proves the caller may see this lab, and therefore its environment. Same reasoning as the lesson-image route. LOW-BANDWIDTH MODE (P02 Wave K), for the markets the payment rails actually serve. An 8K panorama is ~3.8 MB, which is a real cost on a me…
  • POST /education/lessons/{lesson_id}/move · scope education:writeMove Lesson
  • POST /education/lessons/{lesson_id}/reparent · scope education:writeReparent LessonMove a lesson into a different module of the SAME course.
  • GET /education/lessons/{lesson_id}/scenario · scope education:readGet Scenario
  • PUT /education/lessons/{lesson_id}/scenario · scope education:writeSet Scenario
  • POST /education/lessons/{lesson_id}/scenario/preview · scope education:writePrepare Lab ScenePrepares BOTH halves of a real-life lab (plan v9, addition 23): the interactive scene and its photoreal 360° environment. Either half that already exists is kept; the environment failing NEVER fails the lab - a missing picture must not block mastery.
  • GET /education/lessons/{lesson_id}/sections · scope education:readList SectionsA lesson's sections, in order. An empty list is the honest answer for every lesson that predates this wave - and the reason the level could be added to live data at all.
  • POST /education/lessons/{lesson_id}/sections · scope education:writeCreate Section
  • DELETE /education/modules/{module_id} · scope education:writeDelete ModuleRemove a module - and everything under it, which is why it refuses once a student has done anything inside it (Tenant Zero, 2026-08-26: the Emperor typed a module title into the wrong box and had no way to take it back).
  • PATCH /education/modules/{module_id} · scope education:writeRename Module
  • GET /education/modules/{module_id}/coverage · scope education:readModule CoverageDoes this module's lab, project and exam test everything its sections and lessons teach? The Emperor's rule, made checkable. `gaps` is the list a builder shows in words: "two competencies this module teaches are never tested."
  • GET /education/modules/{module_id}/lessons · scope education:readList Lessons
  • POST /education/modules/{module_id}/lessons · scope education:writeCreate Lesson
  • POST /education/modules/{module_id}/move · scope education:writeMove Module
  • GET /education/modules/{module_id}/shape · scope education:readModule ShapeWhat shape is this module, right now? Shown before saving, so nobody names a template after a module without first seeing what they are about to hold everything else to.
  • POST /education/modules/{module_id}/students/{student_user_id}/certificate · scope education:writeIssue Module CertificateA module is a mini course, so mastering one is worth showing. REFUSED UNLESS THE MODULE IS ACTUALLY MASTERED, by the platform's own existing rule - the same `_is_module_complete` that gates everything else, so a credential can never mean less than the progress bar beside it.
  • GET /education/my/community · scope education:readMy CommunityThe learner's whole community in one reading: streak and badges (awarded from real completions on this very read), the published word from every school they study at, upcoming published events, and their own rooms. A student with nothing yet gets honest zeros and a standing invitation, never a blank.
  • GET /education/my/progress/{scope}/{scope_id} · scope education:readMy ProgressWhat THIS learner can now do, at whichever level they are standing.
  • GET /education/ops/schools/{school_id}/analytics · scope education:readSchool AnalyticsSchool analytics, optionally scoped to one academic term (P02 Wave M). WHAT `term_id` SCOPES, AND WHAT IT DELIBERATELY DOES NOT. Enrolments and attendance become term-scoped: an enrolment stores its term (migration 397) and attendance is filtered by the term's date window - the same two grains the Registrar uses, so a school's summary can never disagree with the report cards it is a summary of. S…
  • GET /education/ops/schools/{school_id}/export/roster.csv · scope education:readExport Roster CsvEvery enrollment in the school, with the grade and attendance facts beside it. EMPTY CELLS, NOT ZEROS. A student with nothing scored gets a blank score column, because a spreadsheet full of zeros is a spreadsheet that will be averaged - and an average that counts "not yet marked" as nought is a lie a school might act on.
  • GET /education/ops/schools/{school_id}/today · scope education:readClasses Today
  • POST /education/overrides · scope education:writeGrant OverrideA teacher lets a student past one gate, with a reason. THE HAND IS A PERSON'S. Wave F ruled that the tutor coaches and never grades; opening a gate is a grading decision in everything but name, so no model makes it. The reason is required by the column, by this endpoint and by the screen - an override with no reason is indistinguishable from a mistake, and a student's file should say who decided …
  • GET /education/program-certificates/mine · scope education:readList My Program Certificates
  • POST /education/program-certificates/{certificate_id}/revoke · scope education:writeRevoke Program Certificate
  • GET /education/program-certificates/{certificate_id}/verify · scope education:readVerify Program Certificate
  • POST /education/programme-templates · scope education:writeCreate Programme TemplateBless one programme and make its shape the standard for the others. The same three refusals the module level makes, for the same reasons. A programme with no courses describes nothing and every comparison against it would report "matches" - success it never checked.
  • GET /education/programme-templates/{template_id}/compare/{program_id} · scope education:readCompare ProgrammeHow does this programme differ from the shape? Facts only; nothing is changed.
  • GET /education/programmes/{program_id}/shape · scope education:readProgramme ShapeWhat shape is this programme, right now? Shown before saving, for the same reason the module one is: nobody should name a standard after a programme without seeing the numbers first.
  • GET /education/programs/{program_id} · scope education:readGet Program
  • PATCH /education/programs/{program_id} · scope education:writeUpdate Program
  • GET /education/programs/{program_id}/certificates · scope education:readList Program Certificates
  • POST /education/programs/{program_id}/certificates/{student_user_id} · scope education:writeIssue Program CertificateReal, computed program-completion eligibility - never fabricated: a program-wide certificate can only be issued once every linked course already has a real, non-revoked certificate.course row for this exact student. A program with zero linked courses can never be "complete" - rejected the same as a program still missing a real course certificate, not treated as a vacuous pass.
  • POST /education/programs/{program_id}/courses · scope education:writeAttach Program Course
  • DELETE /education/programs/{program_id}/courses/{course_id} · scope education:writeDetach Program Course
  • POST /education/programs/{program_id}/enroll · scope education:writeEnroll In Program
  • GET /education/progress/mine · scope education:readMy Progress
  • GET /education/prose/{scope}/{scope_id} · scope education:readProse At ScopeThe written content under one scope, for checks that must read the prose itself. The review reads TITLES - it can say a section is empty, duplicated or untested, and nothing at all about whether what is written in it is true. A worked example on production concluded that a 32 V reading against a 36 V expectation with a +/-2 V tolerance was "within expected parameters" (2026-08-27). Every structur…
  • PUT /education/questions/{question_id} · scope education:writeEdit Question
  • GET /education/quizzes/{quiz_id}/my-result · scope education:readMy Quiz Result
  • POST /education/quizzes/{quiz_id}/proctoring-events · scope education:writeLog Proctoring Event
  • GET /education/quizzes/{quiz_id}/proctoring-events/mine · scope education:readMy Proctoring Events
  • GET /education/quizzes/{quiz_id}/proctoring-events/{student_user_id} · scope education:readStudent Proctoring Events
  • GET /education/quizzes/{quiz_id}/proctoring-report · scope education:readProctoring Report
  • GET /education/quizzes/{quiz_id}/questions · scope education:readList Questions
  • POST /education/quizzes/{quiz_id}/questions · scope education:writeAdd Question
  • GET /education/quizzes/{quiz_id}/results · scope education:readQuiz Results
  • GET /education/quizzes/{quiz_id}/scenarios · scope education:readList ScenariosEvery scenario in this assessment, each with its own questions already attached. Nested rather than flat because a stem without its questions is unusable and a question without its stem is unreadable - a caller that had to join them itself would eventually render one without the other.
  • POST /education/quizzes/{quiz_id}/scenarios · scope education:writeCreate Scenario
  • POST /education/quizzes/{quiz_id}/submit · scope education:writeSubmit Quiz
  • POST /education/results/{result_id}/amend · scope education:writeAmend ResultCorrect a sealed result by issuing a NEW one that supersedes it. THE OLD ROW IS NEVER TOUCHED. It stays exactly as issued, signature intact, because the point of an academic record is that you can still see what was said before the correction. That is also the only design compatible with M2's grant, which is SELECT and INSERT only - the application cannot modify a sealed row even if it wanted to.…
  • GET /education/review/{scope}/{scope_id} · scope education:readReview At ScopeTHE SAME QUESTION AT FOUR LEVELS — the Emperor's architecture (2026-08-27). "When we use them in the course page is to discover and auto fix/cover gaps from the Modules' headings... in the module page... from the Lessons' headings... in the Lessons' page... from the sections' headings... in the section page... from the section's contents to make sure all competencies will be cover…
  • DELETE /education/scenarios/{scenario_id} · scope education:writeDelete ScenarioRefused once anybody has answered its questions. The same rule as a module, a lesson and a section: a teacher's tidy-up never erases a student's record. Deleting the stem would cascade to its questions, and their answers are part of somebody's result.
  • GET /education/schools · scope education:readList Schools
  • POST /education/schools · scope education:writeCreate School
  • GET /education/schools/mine · scope education:readGet My School Endpoint
  • POST /education/schools/{school_id}/announcements · scope education:writeCreate Announcement
  • GET /education/schools/{school_id}/announcements · scope education:readList AnnouncementsRLS does the honest split: staff see drafts, an enrolled student sees only the published ones, a stranger sees none.
  • POST /education/schools/{school_id}/announcements/draft-digest · scope education:writeDraft Weekly DigestAUTOMATION WITH ITS HANDS TIED: a deterministic reading of the school's own week - new students, discussion activity, upcoming events, live classes - assembled into a DRAFT announcement. NO MODEL writes it (the growth-advisor doctrine), and it goes nowhere until a person publishes. Every number below is a count of real rows.
  • GET /education/schools/{school_id}/courses · scope education:readList Courses
  • POST /education/schools/{school_id}/courses · scope education:writeCreate Course
  • GET /education/schools/{school_id}/members · scope education:readList School Members
  • POST /education/schools/{school_id}/members · scope education:writeAdd School Member
  • PATCH /education/schools/{school_id}/members/{member_id}/grade-level · scope education:writeSet Grade Level
  • GET /education/schools/{school_id}/programs · scope education:readList School Programs
  • POST /education/schools/{school_id}/programs · scope education:writeCreate Program
  • GET /education/schools/{school_id}/subscriptions · scope education:readSchool SubscriptionsA sub-portal school's own adopted curricula (with a behind flag).
  • GET /education/schools/{school_id}/terms · scope education:readList Terms
  • POST /education/schools/{school_id}/terms · scope education:writeCreate Term
  • PATCH /education/schools/{school_id}/terms/{term_id} · scope education:writeUpdate Term
  • DELETE /education/schools/{school_id}/terms/{term_id} · scope education:writeDelete TermDelete a term - unless it is holding academic history (Wave M, migration 398). THE REFUSAL IS CHECKED HERE AND ENFORCED BY THE DATABASE, which is deliberate belt and braces for two different failures. The count below produces a sentence a person can act on; the `ON DELETE RESTRICT` constraint behind it is what actually holds if a future caller forgets to ask, or if an enrolment is created between…
  • GET /education/schools/{slug} · scope education:readGet School By Slug
  • PATCH /education/sections/{section_id} · scope education:writeUpdate Section
  • DELETE /education/sections/{section_id} · scope education:writeDelete SectionRefused once a student has completed it - the same rule as a module or a lesson (Tenant Zero, 2026-08-26): a teacher's tidy-up never erases a student's record.
  • GET /education/sections/{section_id}/blocks · scope education:readList Section Blocks
  • POST /education/sections/{section_id}/complete · scope education:writeComplete SectionA student marks their own section done. BY THEIR OWN HAND ONLY - the RLS INSERT policy refuses a row written for anybody else, the same rule Wave D drew for guardian consent.
  • POST /education/sections/{section_id}/move · scope education:writeMove SectionMove a section one place, and renumber the whole lesson while we are here. WHY RENUMBER EVERY TIME. Accepting the AI's suggested headings left positions 0, 2, 4, 6, 8 in the Emperor's real course: each suggestion button carried the position the list had when it was drawn, and the list grew under it (2026-08-26). Nothing was broken - order is by position - but gaps invite a future collision, and a…
  • POST /education/sections/{section_id}/reparent · scope education:writeReparent SectionMove a section under a different lesson of the SAME course. A student's completion of this section travels with it - `lesson_section_completion` points at the section, not at where it used to live - so moving a section never erases work.
  • GET /education/students/{student_user_id}/overrides · scope education:readList OverridesEvery gate opened for this student, and why. Readable BY THE STUDENT as well as by staff - a person is entitled to know what was decided about them.
  • GET /education/students/{student_user_id}/report-card · scope education:readReport CardA report card for one student in one term - available at ANY time, which is the point. Nothing here waits for a term to end. A parent asking in week three gets week three's answer, labelled as provisional, rather than "come back in December".
  • GET /education/students/{student_user_id}/transcript · scope education:readTranscriptA student's full academic record across every school, for the people entitled to read it. NO CONSENT IS INVOLVED HERE and that is deliberate. This is the student reading their own record (or their school's staff, or a minor's guardian) through `term_result`'s own policies. Consent governs SHARING - a different act, a different audience, and the next endpoint.
  • DELETE /education/study-group-messages/{message_id} · scope education:writeRemove Group Message
  • POST /education/study-groups/{group_id}/join · scope education:writeJoin Study Group
  • POST /education/study-groups/{group_id}/leave · scope education:writeLeave Study Group
  • GET /education/study-groups/{group_id}/messages · scope education:readList Group Messages
  • POST /education/study-groups/{group_id}/messages · scope education:writePost Group Message
  • GET /education/submissions/mine · scope education:readList My Submissions
  • PATCH /education/submissions/{submission_id} · scope education:writeGive Feedback
  • POST /education/subscriptions/{subscription_id}/sync · scope education:writeSync Subscription
  • POST /education/templates · scope education:writeCreate TemplateBless one module and make its shape the standard.
  • GET /education/templates · scope education:readList TemplatesEvery template this reader can see: their own school's, and the PLATFORM standard for the kind of school they run. RLS decides which, and deliberately so - a tenant is held to the platform template for their category, so they must be able to read it, and they must not see another school's. Platform templates sort first because they are the standard the rest are variations on.
  • DELETE /education/templates/{template_id} · scope education:writeDelete Template
  • GET /education/templates/{template_id}/compare/{module_id} · scope education:readCompareHow does this module differ from the shape? Facts only; nothing is changed. Worded as comparison throughout - "has four lessons where the template has five", never "is missing a lesson". The teacher decides whether the difference is a fault, and on a genuinely smaller topic it is not.
  • POST /education/templates/{template_id}/promote · scope education:writePromote TemplateMake one school's template the standard for a KIND of school. Owner only. WHY PROMOTION RATHER THAN AUTHORING. A template is DERIVED from a real module, never typed - that is what stops it becoming an aspiration nobody has met. The owner runs no school and so has no module to read a shape from, which leaves exactly one honest way to create a platform standard: take one a tenant has already met, a…
  • GET /education/terms/{term_id}/report-cards · scope education:readTerm Report CardsEvery student's standing in one term, for that school's staff - the registrar's own view. SEALED AND UNSEALED TERMS BOTH ANSWER, and the difference is reported rather than hidden. An unsealed term computes live (so it moves, and `sealed` says so); a sealed term reads the frozen rows. A screen that only worked after sealing would be useless during the term it is about.
  • POST /education/terms/{term_id}/seal · scope education:writeSeal TermFreeze this term's results, so a transcript stops moving when a teacher regrades. STAFF ONLY, and enforced by the INSERT policy on `term_result` rather than only by the check below - the check produces a sentence, the policy is what holds. SEALING IS ONE-WAY AND ONE-TIME. A second seal is refused rather than overwriting, because overwriting is precisely the thing this table exists to make imposs…
  • POST /education/transcript/shares · scope education:writeCreate Transcript ShareMint a shareable transcript - the student's own act, and nobody else's. THE SCHOOL CANNOT DO THIS FOR THEM. The INSERT policy admits only rows whose `student_user_id` is the caller, so a school publishing a student's record is not merely discouraged, it is impossible through the application. That is the mirror of Wave D, where only a guardian could consent for a minor; here only the student can c…
  • GET /education/transcript/shares/mine · scope education:readMy Transcript SharesEvery transcript link this student has ever minted, withdrawn ones included. "Who have I sent my grades to?" is a question a person is entitled to answer later, which is why 402 has no DELETE policy: revoking a share withdraws the ACCESS and keeps the RECORD. A list that quietly dropped withdrawn shares would answer a different, more comfortable question. Scoped by the caller's own RLS rather th…
  • POST /education/transcript/shares/{share_id}/revoke · scope education:writeRevoke Transcript ShareWithdraw a shared transcript. Immediate, and the record that it existed stays. There is no DELETE policy on the table on purpose: "who have I sent my grades to?" is a question a student is entitled to answer later, and deleting the row would erase the answer along with the access.
  • GET /education/translations/pending-review · scope education:readList Pending ReviewEvery draft_ready translation this user may act on, across ALL their schools. WHY THIS EXISTS. `mark-reviewed` has always been here, and so has the `human_reviewed` state - but the only way to FIND a pending draft was GET /courses/{course_id}/translations, one course at a time. A school with forty courses had no way to see what was waiting, so in practice nothing ever got reviewed and `human_revi…
  • DELETE /education/translations/{translation_id} · scope education:writeDelete Translation
  • POST /education/translations/{translation_id}/create-course · scope education:writeCreate Course From TranslationMaterializes a real, human-reviewed translation into a genuine, independent `course` row (with real modules and lessons) - a real sibling of the original, students can actually enroll in and study, not just translated text sitting in a draft table. Only reachable from status='human_reviewed' - the explicit mark_reviewed step above is mandatory first. Each translation can only be materialized once …
  • PATCH /education/translations/{translation_id}/mark-reviewed · scope education:writeMark ReviewedA human explicitly confirming the MT draft has been reviewed - never automatic, matching this project's standing 'AI drafts, never authoritative' pattern. Education has no structured review_cycle system the way Publishers does (a genuine, real scoping difference, not an oversight - Education's own editorial model is simpler: draft/published/archived with no reviewer roles), so this is a plain, dir…
  • GET /education/translations/{translation_id}/modules · scope education:readGet Translated ContentThe real, structured translated preview - every module with its own real translated lessons nested inside, in order - what a reviewer actually reads before deciding to materialize it into a real course.
  • GET /education/tutor/courses/{course_id}/messages · scope education:readGet Tutor History
  • POST /education/tutor/courses/{course_id}/messages · scope education:writeSend Tutor Message
  • GET /education/tutor/courses/{course_id}/sessions · scope education:readList Course Tutor SessionsThe educator view's index: every tutor session in this course. Staff-gated at the endpoint for a clean sentence; RLS (390) is the real control on every row.
  • GET /education/tutor/sessions/{session_id}/messages · scope education:readGet Session MessagesOne transcript. RLS decides who this answers for: the student themself, staff of the course's school, a minor student's guardian, or the owner - anyone else gets an honest 404 because for them the session does not exist.
  • GET /education/tutor/students/{student_user_id}/sessions · scope education:readList Student Tutor SessionsThe guardian view's index: a linked MINOR student's tutor sessions, with course titles in student_name's place left to the client. RLS (390) admits rows only for the student, their school staff, a minor's guardian, or the owner - an empty list is the honest answer for everyone else.
  • POST /education/tutor/{course_id}/messages/{message_id}/speak · scope education:writeSpeak Tutor MessageThe character speaks their line aloud (Wave N, N4) - the same counsel guard as the Concierge's /speak, same shape exactly: only a SERVER-OWNED line is ever voiced, here a stored assistant reply from the caller's OWN tutor session (RLS scopes the SELECT, and the role filter means a student cannot make the voice read their own words back as if the platform said them). Voice ladder identical to the C…
  • GET /staff-growth/benefit-plans · scope education:readList Benefit Plans
  • POST /staff-growth/benefit-plans · scope education:writeCreate Benefit PlanThe owner defines the catalog - 'all types' by construction, because a type is a row. Fitinty records elections and exports them; the CARRIER administers the benefit.
  • GET /staff-growth/cadence-alerts · scope education:readCadence AlertsEVERY ALERT CARRIES A PROPOSED ACTION (the standing rule). Three kinds, computed on read from real rows: a review past its cadence, a role's required training missing or unfinished, a certification aged past recert_days. A quiet list means the checks RAN and found nothing - the response says so rather than returning a bare empty list.
  • GET /staff-growth/cadence-settings · scope education:readGet Cadence
  • POST /staff-growth/cadence-settings · scope education:writeSet CadenceThe owner's dial. NULL/absent = no cadence - an org that reviews ad-hoc is a choice, not a defect, so no alert fires for it.
  • GET /staff-growth/documents/{document_id}/file · scope education:readRead Document
  • GET /staff-growth/employees/{employee_id}/compensation · scope education:readList CompensationOwner-only BY RLS: anyone else gets an empty list, and an empty list here means 'not yours to see' as much as 'none recorded' - the endpoint cannot tell you which.
  • POST /staff-growth/employees/{employee_id}/compensation · scope education:writeRecord Compensation
  • GET /staff-growth/employees/{employee_id}/documents · scope education:readList Documents
  • POST /staff-growth/employees/{employee_id}/documents · scope education:writeUpload DocumentCUSTODY, not generation: the paystub or W-2 the tenant's real payroll provider issued gets a home the employee and the owner can both reach - Fitinty computed none of it and never will from here.
  • POST /staff-growth/employees/{employee_id}/elections · scope education:writeElect BenefitThe employee's own act (their login, or the owner recording a paper election). Electing a closed plan is refused - the window is the owner's dial.
  • GET /staff-growth/employees/{employee_id}/elections · scope education:readList Elections
  • POST /staff-growth/employees/{employee_id}/enroll · scope education:writeEnroll EmployeeOne click from the roster into the org's OWN school - Education's machinery does the rest (spine, competencies, coverage, credentials). Never a parallel training system.
  • GET /staff-growth/employees/{employee_id}/offboarding · scope education:readOffboardingThe checklist materializes from the org's template (seeded with honest defaults on first use) the first time a terminated employee's is read.
  • POST /staff-growth/employees/{employee_id}/offboarding/done · scope education:writeOffboarding Done
  • GET /staff-growth/employees/{employee_id}/orientation · scope education:readOrientationEH1 - THE DAY-ONE PACKET. The checklist (offboarding's mirror, materialized on first read) plus the real facts a new person needs: who their manager is and what training their role requires - read from rows, never invented.
  • POST /staff-growth/employees/{employee_id}/orientation/done · scope education:writeOrientation Done
  • GET /staff-growth/employees/{employee_id}/reviews · scope education:readList Reviews
  • POST /staff-growth/employees/{employee_id}/reviews · scope education:writeCreate Review
  • GET /staff-growth/employees/{employee_id}/screening · scope education:readList Screening
  • POST /staff-growth/employees/{employee_id}/screening · scope education:writeRecord ScreeningHis question, his own hard line: eligibility-to-work and background checks are recorded as FACTS - kind, status, date, category note. Never a number.
  • GET /staff-growth/employees/{employee_id}/training · scope education:readEmployee TrainingThe roster's honest training view: the employee's enrollments in THIS org's courses, each with its status. No login yet = no training possible, said in words.
  • GET /staff-growth/employees/{employee_id}/transitions · scope education:readList Transitions
  • POST /staff-growth/employees/{employee_id}/transitions · scope education:writeRecord TransitionA promotion is a RECORDED transition that itself applies the change - one writer, so the history and the record can never disagree.
  • POST /staff-growth/employees/{employee_id}/verification · scope education:writeCreate Verification"Worked here, X to Y" on the same signature discipline as certificates. Works for current AND former employees - the reference an ex-employee actually needs. Revoke by deleting the row; the public link then answers not-found, never a fabricated 'valid'.
  • GET /staff-growth/employees/{employee_id}/warnings · scope education:readList Warnings
  • POST /staff-growth/employees/{employee_id}/warnings · scope education:writeCreate Warning
  • GET /staff-growth/my-work · scope education:readMy WorkONE honest view of the signed-in person's working life: their employee records, what waits for their hand (reviews to sign, warnings to acknowledge), their documents and their courses. Sections whose waves have not landed (shifts, the ID card) are simply absent - never a fake placeholder.
  • GET /staff-growth/org-courses · scope education:readOrg CoursesWhat this org can train ON: its own schools' courses (any type - a store's training school is just a school whose type says so).
  • GET /staff-growth/payroll-export · scope education:readPayroll ExportTHE PAYROLL-READY EXPORT - the boundary made useful. Hours from the ONE time arithmetic, the latest compensation row, and elected benefits, per employee, as CSV a real payroll provider consumes. NO tax math, NO withholdings, NO net pay - the header says so, so the file can never be mistaken for a payroll run. Owner-only.
  • POST /staff-growth/reviews/{review_id}/acknowledge-offline · scope education:writeAcknowledge OfflineFor the record-only worker with no login: the owner records that the review was read together, with a note that must exist - a silent checkbox would be a secret file with extra steps.
  • POST /staff-growth/reviews/{review_id}/share · scope education:writeShare ReviewDraft -> shared: from this moment the employee's own login can read it (RLS's owner-or-self policy) and only THEY can sign it.
  • POST /staff-growth/reviews/{review_id}/sign · scope education:writeSign ReviewTHE EMPLOYEE signs - nobody else. The check is identity, not role: the signer's login must be the one linked to the reviewed employee.
  • GET /staff-growth/role-requirements · scope education:readList Requirements
  • POST /staff-growth/role-requirements · scope education:writeAdd Requirement
  • DELETE /staff-growth/role-requirements/{requirement_id} · scope education:writeRemove Requirement
  • POST /staff-growth/warnings/{warning_id}/acknowledge · scope education:writeAcknowledge WarningThe employee acknowledges - identity, not role, exactly like signing a review.
Marketplace - 106 doors - marketplace:read, marketplace:write

The commerce spine: listings, carts, orders and vendors, shared by every pillar that sells.

  • GET /counter/products · scope marketplace:readCounter ProductsWhat the counter can ring up: the org's PUBLISHED products from the same mirror the storefront serves - one catalog, two doors.
  • POST /counter/sales · scope marketplace:writeRecord Sale
  • GET /counter/sales · scope marketplace:readList Sales
  • POST /customer-issues · scope marketplace:writeRaise IssueThe customer's door. Recorded, told to the book, counted on the advisor - executed by nobody.
  • GET /customer-issues/business/{organization_id} · scope marketplace:readBusiness IssuesThe business's side: open first. Membership asked IN THE BODY, in words - the wall would answer with an empty list, which reads exactly like a business nobody has asked anything of.
  • GET /customer-issues/mine · scope marketplace:readMy IssuesThe person's own requests, on their own cursor (the self arm); business names on the system cursor.
  • POST /customer-issues/{issue_id}/answer · scope marketplace:writeAnswer IssueThe business answers in words; closing needs a reason (the row's own CHECK). The customer's words are never touched. Told to the book so the answer sits beside the request.
  • GET /disputes · scope marketplace:readDispute InboxONE inbox. Opening it IS the sweep - no daemon to trust, and the note says when and what was read. Every dispute carries its proposed action.
  • POST /disputes/{dispute_id}/assemble · scope marketplace:writeAssemble
  • POST /disputes/{dispute_id}/draft · scope marketplace:writeDraft ResponseAI DRAFTS, GROUNDED IN THE ASSEMBLED PACK ONLY - it runs after the readers, refers to nothing outside them, and what it writes stays a draft until the owner submits.
  • PATCH /disputes/{dispute_id}/note · scope marketplace:writeEdit Note
  • POST /disputes/{dispute_id}/submit · scope marketplace:writeSubmit ResponseTHE OWNER'S HAND. Pushes the pack + draft + note to Stripe as the dispute's evidence, on the TENANT'S OWN account. Final: the card network reads what is sent.
  • GET /marketplace/affiliate/clickbank-config · scope marketplace:readGet Clickbank Config
  • POST /marketplace/affiliate/clickbank-config · scope marketplace:writeSave Clickbank ConfigReal upsert - saving a config a second time keeps the SAME real webhook_token (it's the org's own permanent webhook address, already pasted into their live ClickBank account) but rotates the secret key, same "replace the stored secret" convention as the other six networks' own credential save endpoint.
  • GET /marketplace/affiliate/clickbank-transactions · scope marketplace:readList Clickbank Transactions
  • GET /marketplace/affiliate/network-credentials · scope marketplace:readList Network CredentialsReal, per-org configured network connections. Never returns a secret - only the connection/sync bookkeeping a caller needs to decide whether a network is already set up and whether its last real sync succeeded.
  • POST /marketplace/affiliate/network-credentials · scope marketplace:writeSave Network CredentialReal upsert - saving a credential for a network the org has already configured replaces the stored secret rather than creating a duplicate row (the real UNIQUE(organization_id, network) constraint is what enforces this, not just application logic).
  • DELETE /marketplace/affiliate/network-credentials/{network} · scope marketplace:writeDelete Network Credential
  • GET /marketplace/affiliate/network-credentials/{network}/catalog · scope marketplace:readSearch Network CatalogReal, on-demand product/ad catalog search using the org's own stored credential - genuinely distinct from .../sync above (which reads a vendor's own PAST commission history). Never writes to last_synced_at/last_sync_status/last_sync_detail - a catalog search is a read-only lookup, not a sync event. For jvzoo/warriorplus/ rakuten_advertising this honestly reports that no real catalog endpoint could…
  • POST /marketplace/affiliate/network-credentials/{network}/sync · scope marketplace:writeSync Network CredentialRuns the real, network-specific API call using the org's own stored credential - never a mocked or fabricated result. A real network/auth failure is persisted honestly as last_sync_status='error' with the actual error text, not silently swallowed or reported as success. Combined Roadmap row 6: a real successful sync's own total_amount/count (already computed by sync_network(), previously thrown a…
  • GET /marketplace/affiliate/offers · scope marketplace:readList Published Offers
  • POST /marketplace/affiliate/offers · scope marketplace:writeCreate Offer
  • GET /marketplace/affiliate/offers/mine · scope marketplace:readList My Offers
  • PATCH /marketplace/affiliate/offers/{offer_id} · scope marketplace:writeUpdate Offer
  • POST /marketplace/affiliate/offers/{offer_id}/click · scope marketplace:writeRecord Click
  • GET /marketplace/affiliate/offers/{offer_id}/clicks · scope marketplace:readCount Clicks
  • POST /marketplace/affiliate/offers/{offer_id}/promo-drafts · scope marketplace:writeDraft Promo ContentRow 5 - AI-drafted affiliate promo content, reusing Content Factory's real Creative Agent (draft_creative_json) and Visual Agent (generate_visual_asset) directly, imported from marketing_content_factory rather than duplicated here. The "research" grounding is the offer's own real merchant/product/disclosure fields - genuinely real connected data, not a knowledge-base search (there's no marketing_p…
  • GET /marketplace/affiliate/offers/{offer_id}/promo-drafts · scope marketplace:readList Promo Drafts
  • GET /marketplace/affiliate/promo-drafts/{asset_id}/image · scope marketplace:readDownload Promo Image
  • PATCH /marketplace/affiliate/promo-drafts/{asset_id}/status · scope marketplace:writeSet Promo Status
  • POST /marketplace/affiliate/webhooks/clickbank/{webhook_token} · scope marketplace:writeReceive Clickbank NotificationThe real, unauthenticated (no Fitinty session - ClickBank itself calls this) receiver ClickBank's own INS posts to. Runs under `db.system_cursor`'s owner-bypass RLS context, same as OnlyOffice's own server-callback endpoints - real access control here is the webhook token itself (an unguessable per-org secret path segment) PLUS the AES decryption succeeding at all, which is only possible with the …
  • POST /marketplace/digital-assets · scope marketplace:writeUpload Digital Asset
  • GET /marketplace/digital-assets/by-product/{medusa_product_id} · scope marketplace:readGet Asset By ProductIs this product a digital download - asked by the BUYER'S OWN ORDER SCREEN, once per line item, to decide whether to offer a Download button. READ ON THE SYSTEM CURSOR SINCE 585, and the reason is the whole design: the row is walled to the SELLING organization, because a buyer's right to a file is a Medusa receipt and no Postgres predicate can see it. Left on the caller's cursor this would answer…
  • GET /marketplace/digital-assets/mine · scope marketplace:readList My Assets
  • DELETE /marketplace/digital-assets/{asset_id} · scope marketplace:writeDelete Digital Asset
  • GET /marketplace/digital-assets/{asset_id}/download · scope marketplace:readDownload Digital Asset
  • GET /marketplace/integrations · scope marketplace:readList IntegrationsReal status for the integrations this pillar actually has - not a catalog of connectors that could theoretically exist. Extend this list only when a new integration is genuinely wired in, the same discipline already applied to Workspace's System Health page.
  • POST /marketplace/landing · scope marketplace:writeUpsert LandingCreate or rewrite the page as a DRAFT - publishing is its own deliberate door.
  • GET /marketplace/landing/mine · scope marketplace:readMy Landings
  • POST /marketplace/landing/{landing_id}/publish · scope marketplace:writePublish Landing
  • GET /marketplace/luxury/provider-status · scope marketplace:readProvider Status
  • POST /marketplace/luxury/requests · scope marketplace:writeCreate Request
  • GET /marketplace/luxury/requests · scope marketplace:readList Requests
  • POST /marketplace/luxury/requests/{request_id}/cancel · scope marketplace:writeCancel Request
  • POST /marketplace/luxury/requests/{request_id}/record-result · scope marketplace:writeRecord ResultOwner-only. Records a REAL provider verdict (a genuine Entrupy certificate). This is the ONLY way a request reaches an authenticated/not_authentic/inconclusive status — never fabricated by code. Requires a real certificate id so a verdict can always be traced to a real authentication.
  • GET /marketplace/orders/mine · scope marketplace:readList My OrdersA buyer's own orders, read from Medusa. WHAT WAS WRONG. This queried `marketplace_order`, a table dropped when the Marketplace moved to Medusa - `marketplace_return` carries a plain `medusa_order_id TEXT` rather than a foreign key for exactly that reason. Every call raised `relation "marketplace_order" does not exist`, so the endpoint answered 500 for every signed-in person. Nothing reported it b…
  • GET /marketplace/promotions/mine · scope marketplace:readMy PromotionsThe seller's shelf, each row's state in words - a lapsed one says so.
  • GET /marketplace/promotions/prices · scope marketplace:readPromo PricesThe picker - platform data, with the billing truth stated per row.
  • POST /marketplace/promotions/request · scope marketplace:writeRequest PromotionThe seller's ask. Their own PUBLISHED product only; one live or pending promotion per product at a time. Born pending_payment - nothing surfaces until activation, and the response says exactly what happens next.
  • POST /marketplace/promotions/{promotion_id}/activate · scope marketplace:writeActivate PromotionTHE EMPEROR'S HAND - the standing comp ruling until Dodo carries slot products. Stamped 'comp': a comped spotlight is never dressed as revenue, and NO ledger row is written (the ledger records captured payments, never favors).
  • POST /marketplace/rd · scope marketplace:writeCreate Rd
  • GET /marketplace/rd · scope marketplace:readList Rd
  • POST /marketplace/rd/{project_id}/advance · scope marketplace:writeAdvance RdOne stage forward, never skipping - and NEVER without a real note, because the stages that matter (sample, test, package) are HUMAN handshakes with suppliers and manufacturers; the record tracks them, it does not fake them.
  • POST /marketplace/returns · scope marketplace:writeRequest Return
  • GET /marketplace/returns/mine · scope marketplace:readList My Returns
  • GET /marketplace/returns/to-review · scope marketplace:readList Returns To ReviewFetches every pending return, then asks Medusa (via this vendor's own session) which underlying orders they can actually see - Medusa's own store-scoping does the real per-vendor narrowing here, since orders aren't tagged with a Fitinty-visible tenant_id the way our own tables are. See migration 011's comment for the reasoning.
  • PATCH /marketplace/returns/{return_id} · scope marketplace:writeUpdate Return Status
  • GET /marketplace/suppliers · scope marketplace:readList Suppliers
  • POST /marketplace/suppliers · scope marketplace:writeCreate Supplier
  • POST /marketplace/suppliers/sweep · scope marketplace:writeSweep FeedsM3 - THE STANDING SWEEP, made real. Every ACTIVE url_feed and fitinty-partner supplier across the platform syncs through the ONE pipeline; each supplier's failure lands on its own row in words and never blocks the others. Owner-only: this is the Emperor's timer's door (24/7 decree - the doctor timer hits it; the launch checklist carries the systemd entry). A tenant syncs their own supplier from th…
  • PUT /marketplace/suppliers/{supplier_id} · scope marketplace:writeUpdate Supplier
  • POST /marketplace/suppliers/{supplier_id}/forward · scope marketplace:writeForward OrderSANDBOX. Records the dropship forward the real rail will someday send - practiced end to end, honest about being practice. Nothing leaves the platform; the row says simulated_sent and every screen that shows it must say so too.
  • POST /marketplace/suppliers/{supplier_id}/sync · scope marketplace:writeSync NowSync one supplier now. A csv_feed takes the uploaded sheet; a url_feed fetches its URL (public hosts only, size-capped). The standing sweep (POST /marketplace/suppliers/sweep, the Emperor's timer) runs the same `sync_supplier` for every active url_feed and fitinty partner feed - one pipeline, nothing to drift. (M3 note: an earlier docstring CLAIMED a nightly job that never existed - a promise in p…
  • POST /marketplace/vendor · scope marketplace:writeCreate Vendor
  • GET /marketplace/vendor/all · scope marketplace:readList All Vendors
  • POST /marketplace/vendor/discounts · scope marketplace:writeCreate Discount
  • GET /marketplace/vendor/discounts · scope marketplace:readList DiscountsThe shelf, with REAL redemption counts read from Medusa per code.
  • POST /marketplace/vendor/discounts/{discount_id}/deactivate · scope marketplace:writeDeactivate DiscountThe off switch - flipped in Medusa AND on the mirror, in that order, so a code that Medusa still honors is never shown as off.
  • GET /marketplace/vendor/listing-options · scope marketplace:readListing OptionsWhat the listing form can honestly offer: the currencies real regions price in, and the categories this vendor's own catalog already uses (a picker seeded from their own data, free text allowed - a marketplace this young must not invent a taxonomy nobody asked for).
  • GET /marketplace/vendor/mine · scope marketplace:readGet My Vendor Endpoint
  • GET /marketplace/vendor/orders · scope marketplace:readList Vendor Orders
  • POST /marketplace/vendor/orders/{order_id}/fulfill · scope marketplace:writeFulfill OrderM2 - the seller's hand: every unfulfilled item into one fulfillment. When the order is backed by a PAID rail checkout, the Medusa-side payment is captured too, so the store's own records match the money that really moved - a practice-rail order is never dressed as captured.
  • POST /marketplace/vendor/orders/{order_id}/ship · scope marketplace:writeShip OrderM2 - the tracking number goes on the fulfillment; the buyer's order page shows the shipped state the moment this lands.
  • GET /marketplace/vendor/products · scope marketplace:readList Vendor Products
  • POST /marketplace/vendor/products · scope marketplace:writeCreate Vendor ProductOne listing, full width (LST1): many images (first leads), a real currency, a category, and a draft state. `file` stays accepted so older callers keep working.
  • POST /marketplace/vendor/products/bulk-csv · scope marketplace:writeBulk Csv ImportBulk listings from a CSV (LST3) - the first way a whole catalog enters this platform without typing it in one product at a time. Columns: title (required), description, price, currency, category, status (draft|published, default draft). ALL-OR-NOTHING: every row is validated FIRST and a bad file is refused with each problem named by row number - a partial import that silently skipped rows would r…
  • GET /marketplace/vendor/products/costs · scope marketplace:readList Listing Costs
  • PUT /marketplace/vendor/products/{product_id} · scope marketplace:writeUpdate Vendor ProductThe end of write-once (LST1): title, words, price, currency, category and draft state are all the vendor's to change. Ownership is the vendor's own scoped Medusa session - a product their store cannot see cannot be edited, and the mirror updates in the same transaction so the storefront never serves the old words.
  • DELETE /marketplace/vendor/products/{product_id} · scope marketplace:writeDelete Vendor ProductDeletes the Medusa product; the mirror row is DEACTIVATED, never deleted - history survives and a re-list reactivates it (the mirror's own standing rule).
  • PUT /marketplace/vendor/products/{product_id}/cost · scope marketplace:writeSet Listing CostThe tenant's PRIVATE cost (441) - margin advice needs it, only they know it, and it never touches the public mirror. Ownership refereed by their scoped session as every product write is.
  • POST /marketplace/vendor/products/{product_id}/images · scope marketplace:writeAdd Vendor Product ImagesAdd photos to an existing listing - enhanced through the same pipeline as every image upload on the platform, appended after the ones already there.
  • GET /marketplace/vendor/questions · scope marketplace:readList My Product QuestionsThe seller's queue - unanswered first, because each is a buyer mid-decision. Which is exactly why the cap mattered: the ordering puts the unanswered at the top, so the hundred-and-first question a seller never sees is one somebody is still waiting on.
  • POST /marketplace/vendor/questions/{question_id}/answer · scope marketplace:writeAnswer Product Question
  • POST /marketplace/vendor/questions/{question_id}/hide · scope marketplace:writeHide Product QuestionThe Commons' hand: hidden WITH a reason on the row, never erased - abuse happens, erasure of what was asked is how it compounds.
  • POST /marketplace/vendor/referral-program · scope marketplace:writeSet ProgramThe seller's dial. Existing codes keep working when the program pauses - a code already in a friend's hands is a promise; pausing stops NEW codes only.
  • GET /marketplace/vendor/referral-program · scope marketplace:readMy ProgramThe desk view: the dials, every code, every reward minted.
  • POST /marketplace/vendor/vendor/mirror-sync · scope marketplace:writeSync Public Product MirrorBackfill + resync of the public-storefront product mirror (migration 233). Loops every vendor and lists their products through their OWN scoped Medusa session -- the same mechanism the vendor UI uses, and the only reliable store->products mapping (the admin store_id query param is silently ignored by the marketplace plugin; verified with a bogus-id probe before this was written). Credentials neve…
  • PATCH /marketplace/vendor/{vendor_id}/verify · scope marketplace:writeSet Vendor Verified
  • POST /reviews/course/{course_id} · scope marketplace:writeReview Course
  • POST /reviews/platform · scope marketplace:writeReview PlatformA tenant's word about Fitinty, published where visitors read it - the hard ones too. Proof: a real org membership. One review per membership; a new org earns another voice.
  • POST /reviews/practice/{office_id} · scope marketplace:writeReview PracticeA practice is reviewed by a client who PAID it at least once on the rail (any kind - consultation, deposit, invoice). The proof ref is the client's ROOM: one relationship, one voice (the unique triple). Satisfaction ratings stay private to the practice; this is the public word, verified the same way every other review on the platform is.
  • GET /reviews/reported · scope marketplace:readReported ReviewsThe Emperor's queue: everything reported, nothing yet hidden.
  • POST /reviews/service/{listing_id} · scope marketplace:writeReview Service
  • POST /reviews/{review_id}/hide · scope marketplace:writeHide ReviewTHE EMPEROR'S HAND ALONE, with a reason on the row - including reviews of Fitinty.
  • POST /reviews/{review_id}/report · scope marketplace:writeReport ReviewThe subject's org REPORTS - it cannot hide. A seller who could hide critical reviews would be fake social proof by omission.
  • GET /store-analytics/insights · scope marketplace:readStore InsightsEvery store the caller's organizations run, each with its trend, top products and AOV.
  • POST /storefront-intents/{token}/claim · scope marketplace:writeClaim IntentExecutes a stored intent for the now-authenticated user - the real enrollment / booking / interest write, exactly as the signed-in flows do it. Single-use; expired or unknown tokens 404 (no oracle for guessing tokens).
  • GET /supply/carry · scope marketplace:readWhat I CarryTHE STORE'S OWN READING: every consignment offered to or carried by this business, each line naming its supplier (on the system cursor - 591) and the organ it rides; what is owed per supplier and currency (lines minus the payouts recorded by hand); the payouts recorded; the business's own currency for the screen to print in. A member of another store reads 403 in words.
  • POST /supply/consignments · scope marketplace:writeOfferThe supplier's offer, with the consent words RECORDED; born 'offered'.
  • GET /supply/consignments · scope marketplace:readConsignmentsBoth sides on one door for one of the caller's organizations: what it consigned (and is owed), what it was offered and carries (and owes).
  • POST /supply/consignments/{consignment_id}/accept · scope marketplace:writeAcceptTHE SELLER's word: its retail price, and the thing on ITS store. Only the seller's people accept.
  • POST /supply/consignments/{consignment_id}/decline · scope marketplace:writeDecline
  • POST /supply/consignments/{consignment_id}/stop · scope marketplace:writeStop CarryingTHE STORE'S hand: it stops carrying an accepted consignment WITH A REASON the supplier reads. The product comes down on the store's OWN cursor (it owns the vendor row); what sold stays owed; 'closed' is the CHECK's own word for it, and the supplier's withdraw stays the supplier's.
  • POST /supply/consignments/{consignment_id}/withdraw · scope marketplace:writeWithdrawTHE SUPPLIER's hand, at will: what is unsold comes back and the seller's product comes down.
  • GET /supply/sellers · scope marketplace:readSellersEvery tenant with a real store, so a supplier can offer to one. Names and slugs only.
  • POST /supply/{seller_organization_id}/payouts · scope marketplace:writeRecord PayoutRule 3: the seller RECORDS what it paid a supplier by its own hand; no transfer is made here.
Publishers - 238 doors - publishers:read, publishers:write

Manuscripts to finished books, and the hand-off that turns a book into a course.

  • PATCH /publishers/audiobook-chapters/{ab_chapter_id} · scope publishers:writeUpdate Ab Chapter
  • POST /publishers/audiobook-chapters/{ab_chapter_id}/generate-narration · scope publishers:writeGenerate NarrationReal AI narration for one audiobook chapter, cloned from a real consented voice sample - the actual mechanical gate that has been missing since this pillar's voice-consent capture was built: an ACTIVE consent record belonging to the SAME house, and a real captured sample of that same consented voice, checked here before any generation is ever triggered.
  • POST /publishers/audiobook-chapters/{ab_chapter_id}/recording · scope publishers:writeUpload Recording
  • GET /publishers/audiobook-chapters/{ab_chapter_id}/recording · scope publishers:readDownload Recording
  • PATCH /publishers/audiobook-projects/{project_id} · scope publishers:writeUpdate Audiobook Project
  • POST /publishers/audiobook-projects/{project_id}/assemble-m4b · scope publishers:writeAssemble M4BReal, complete-book M4B assembly - every chapter's own real recording (human-uploaded or AI-generated, mixed formats/codecs are fine, ffmpeg decodes and re-encodes each one) concatenated in real position order into one genuine M4B with real embedded, correctly-titled chapter markers (ffmpeg's own FFMETADATA1 mechanism, timestamps computed from each real chapter's own measured duration, never estim…
  • GET /publishers/audiobook-projects/{project_id}/assembled-m4b · scope publishers:readDownload Assembled M4B
  • GET /publishers/audiobook-projects/{project_id}/chapters · scope publishers:readList Ab Chapters
  • POST /publishers/audiobook-projects/{project_id}/chapters · scope publishers:writeAdd Ab Chapter
  • PATCH /publishers/authors/{author_id} · scope publishers:writeUpdate Author
  • DELETE /publishers/authors/{author_id} · scope publishers:writeDelete Author
  • PATCH /publishers/authors/{author_id}/archive · scope publishers:writeArchive Author
  • GET /publishers/authors/{author_id}/can-delete · scope publishers:readCan Delete Author
  • POST /publishers/authors/{author_id}/photo · scope publishers:writeUpload Author PhotoAccepts an uploaded photo OR a direct camera capture (the frontend's file input uses the `capture` attribute so phones/tablets open their camera app directly, matching the Emperor's "upload, link to phone, tablet, or camera" request - both paths arrive here as a normal multipart file). Every photo is run through enhance_image() before storage - the Emperor's explicit instruction that all images in…
  • GET /publishers/authors/{author_id}/photo · scope publishers:readGet Author Photo
  • POST /publishers/authors/{author_id}/polish-bio · scope publishers:writePolish Author BioAI copy-edit assistance for the author bio (the Emperor's explicit ask - everything else in the pipeline gets AI drafting/polish by default, but the bio and back-cover blurb, both human-typed text fields, had none). Same "AI drafts, never authoritative" shape as writing_voice_profile's own draft endpoint: takes whatever the caller currently has in the field (saved or not), never touches the stored…
  • PATCH /publishers/authors/{author_id}/restore · scope publishers:writeRestore Author
  • GET /publishers/books/{book_id} · scope publishers:readGet Book
  • PATCH /publishers/books/{book_id} · scope publishers:writeUpdate Book
  • DELETE /publishers/books/{book_id} · scope publishers:writeDelete Book
  • PATCH /publishers/books/{book_id}/archive · scope publishers:writeArchive Book
  • GET /publishers/books/{book_id}/audiobook-project · scope publishers:readGet Audiobook Project
  • POST /publishers/books/{book_id}/audiobook-project · scope publishers:writeCreate Audiobook Project
  • GET /publishers/books/{book_id}/can-delete · scope publishers:readCan Delete Book
  • GET /publishers/books/{book_id}/cross-links · scope publishers:readGet Book Cross LinksRow 11 Phase 4's real "concrete cross-promotion hooks" - the exact same real, live-computed resolution already proven and used inside the composed back-cover image (publishers_barcode.py's own three resolve_*_cta_url functions), now also exposed as a plain JSON endpoint so the book detail page can render real, visible, clickable links - not just a QR code baked into a downloadable PNG. Deliberatel…
  • GET /publishers/books/{book_id}/editions · scope publishers:readList Editions
  • POST /publishers/books/{book_id}/editions · scope publishers:writeCreate EditionUNIQUE(book_id, format, language, interior_color_mode) is a real DB constraint enforcing "one format + one language + one interior color = one edition identity" - extended from the spec's original (format, language)-only rule so a house can create BOTH a black-and-white and a color paperback/hardcover/large_print of the same book as two real, separate editions, each able to carry its own real ISBN…
  • POST /publishers/books/{book_id}/lccn-request · scope publishers:writeCreate Lccn Request
  • GET /publishers/books/{book_id}/lccn-worksheet · scope publishers:readGet Lccn Worksheet
  • GET /publishers/books/{book_id}/plan · scope publishers:readBook Plan
  • POST /publishers/books/{book_id}/plan/declare · scope publishers:writeDeclare Book PlanThe author's declaration, at will. Re-declaring ADDS missing cells and never doubles or deletes what stands - removal is its own deliberate door.
  • PATCH /publishers/books/{book_id}/restore · scope publishers:writeRestore Book
  • POST /publishers/books/{book_id}/royalty-splits · scope publishers:writeAdd Split
  • GET /publishers/books/{book_id}/royalty-splits · scope publishers:readList Splits
  • DELETE /publishers/chapter-images/{image_id} · scope publishers:writeDelete Chapter Image
  • GET /publishers/chapter-images/{image_id}/file · scope publishers:readGet Chapter Image File
  • POST /publishers/chapter-versions/{version_id}/restore · scope publishers:writeRestore Chapter VersionReal restore-to-a-prior-chapter-version - mirrors restore_manuscript_ version's own "always auto-snapshot the CURRENT state first" discipline (so the restore is itself reversible), then applies the target version's own stored title/content back onto the real chapter row.
  • PATCH /publishers/chapters/{chapter_id} · scope publishers:writeUpdate Chapter
  • DELETE /publishers/chapters/{chapter_id} · scope publishers:writeDelete Chapter
  • POST /publishers/chapters/{chapter_id}/compute-sage · scope publishers:writeCompute Sage In ChapterReal Sage compute-and-freeze (task #156). Scans `body.content` - the CHAPTER EDITOR'S CURRENT, possibly-unsaved content, not necessarily what's stored in the DB yet, matching the AI polish endpoints' own "operate on whatever the author is currently editing" contract - for every [[sage:expr]] marker, runs each one through a real, genuinely sandboxed Sage container (sage_sandbox.run_sage_code, see t…
  • POST /publishers/chapters/{chapter_id}/draft-code · scope publishers:writeDraft CodeReal AI-drafted Python/Sage code from a plain-English description (task #159/160), meant for insertion into a real Jupyter cell - the author still reviews it and must explicitly click Run before anything executes in the sandbox, matching task #157's own "AI drafts, human triggers execution" boundary exactly. Never executed here - this endpoint only ever returns text.
  • POST /publishers/chapters/{chapter_id}/draft-latex · scope publishers:writeDraft LatexReal AI-drafted LaTeX from a plain-English description (task #159/160) - closes the one real gap flagged once MathLive/SageMath/ Jupyter shipped with zero AI layer. The author still edits it live in MathLive and must explicitly insert/save it - this endpoint only ever returns text, it never touches the database. Gated on real house membership for this specific chapter, same as every other chapter-…
  • GET /publishers/chapters/{chapter_id}/images · scope publishers:readList Chapter Images
  • POST /publishers/chapters/{chapter_id}/images · scope publishers:writeUpload Chapter ImageA real uploaded illustration a chapter can reference inline via [[image:ID]] (see publishers_content_blocks.py) - rendered as a real embedded image in both EPUB and print PDF, not just stored. Run through enhance_image() before storage, same as every other image upload in this pillar.
  • POST /publishers/chapters/{chapter_id}/jupyter/execute · scope publishers:writeExecute Jupyter CellRuns one real cell against an already-started session's live kernel - a later cell genuinely sees state an earlier cell in the SAME session defined, the whole point of task #157 over #156's one-shot primitive. Real ownership is enforced inside jupyter_sandbox.execute_in_session itself (a session belongs to the user who started it), independent of this chapter-level check.
  • POST /publishers/chapters/{chapter_id}/jupyter/start · scope publishers:writeStart Jupyter SessionStarts one real, isolated, long-lived Jupyter kernel container (task #157 - see jupyter_sandbox.py's own module docstring for the full real isolation posture and the real Kernel Gateway investigation behind this design). Opportunistically sweeps any of THIS user's other sessions that have gone idle past the real timeout, so starting a fresh session is also the natural moment stale ones get reaped.
  • POST /publishers/chapters/{chapter_id}/jupyter/stop · scope publishers:writeStop Jupyter SessionExplicitly, immediately tears down the real container - the honest, author-triggered counterpart to the idle-timeout sweep, so a session an author is done with doesn't sit consuming real memory/CPU until the timeout eventually catches it.
  • POST /publishers/chapters/{chapter_id}/polish · scope publishers:writeRun PolishAnalyzes the chapter and REPLACES its still-pending suggestions with the fresh run's results (applied/dismissed history is never touched). Suggestions whose excerpt is not verbatim-findable in the plain text are dropped at creation - the model quoting loosely is a model problem, never the author's.
  • GET /publishers/chapters/{chapter_id}/polish-suggestions · scope publishers:readList Suggestions
  • GET /publishers/chapters/{chapter_id}/versions · scope publishers:readList Chapter Versions
  • POST /publishers/chapters/{chapter_id}/versions · scope publishers:writeSave Chapter VersionReal per-chapter version snapshot (row 9) - the same real, reviewable revision-history discipline as save_manuscript_version below, scoped to just this one chapter so restoring a single scene never touches any other chapter's own, separately-made edits since.
  • PATCH /publishers/comments/{comment_id} · scope publishers:writeUpdate Comment
  • PATCH /publishers/competencies/{competency_id} · scope publishers:writeReview Competency
  • PATCH /publishers/copyright-deposit-alerts/{alert_id}/acknowledge · scope publishers:writeAcknowledge Copyright Deposit Alert
  • PATCH /publishers/course-conversion/{item_id} · scope publishers:writeReview Course Conversion
  • DELETE /publishers/course-conversion/{item_id} · scope publishers:writeDiscard Course ConversionReal discard support - a 'proposed'/'reviewed'/'rejected' item that never became real Education content can be permanently removed. A 'pushed' item has real downstream course/module/lesson rows and real audit history behind it, so it must never be deleted - the 'rejected' status (a lighter 'mark bad, keep visible' alternative, matching manuscript_competency's own canon_state pattern) is the right …
  • POST /publishers/course-conversion/{item_id}/push · scope publishers:writePush Course ConversionBook-to-Course bridge, step 2: creates REAL Education course/module/ lesson rows (migration 024's tables) from an approved mapping. This deliberately reuses Education's own RLS INSERT policies as the real authorization check instead of reimplementing school-staff logic here - a publisher with no admin/teacher role at the target school gets a genuine RLS rejection, not a fake permission check. One-…
  • PATCH /publishers/distribution-records/{record_id} · scope publishers:writeUpdate Distribution Record
  • PATCH /publishers/editions/{edition_id} · scope publishers:writeUpdate Edition
  • DELETE /publishers/editions/{edition_id} · scope publishers:writeDelete Edition
  • PATCH /publishers/editions/{edition_id}/archive · scope publishers:writeArchive Edition
  • GET /publishers/editions/{edition_id}/auto-back-cover · scope publishers:readGenerate Auto Back CoverBack-cover analog of generate_auto_cover above - same real caching fix, same manual-upload-always-wins precedence via the persisted metadata_record.back_cover_image_storage_path.
  • GET /publishers/editions/{edition_id}/auto-cover · scope publishers:readGenerate Auto CoverReal front-cover art, now genuinely CACHED (Phase C of the audit-fix pass, closing the "regenerated randomly on every GET" finding) - the first successful generation for this edition is persisted to metadata_record exactly like a manual upload, so a repeated GET returns the same real image every time instead of a fresh random one. Use POST .../regenerate-cover to deliberately force a genuinely new…
  • POST /publishers/editions/{edition_id}/back-cover · scope publishers:writeUpload Back CoverManual back-cover override - always takes priority over the auto-composed one, same rule as the front cover. Enhanced before storage like every other image upload in this pillar.
  • GET /publishers/editions/{edition_id}/back-cover · scope publishers:readGet Back Cover
  • GET /publishers/editions/{edition_id}/can-delete · scope publishers:readCan Delete Edition
  • POST /publishers/editions/{edition_id}/cover · scope publishers:writeUpload Cover
  • GET /publishers/editions/{edition_id}/cover · scope publishers:readGet Cover
  • GET /publishers/editions/{edition_id}/distribution-records · scope publishers:readList Distribution Records
  • POST /publishers/editions/{edition_id}/distribution-records · scope publishers:writeCreate Distribution RecordA record tracks manual submission status per platform - it never calls a real KDP/Ingram API. Real auto-publishing to external marketplaces means real seller credentials and putting content up for real public sale, which this platform's spec keeps locked until explicit sign-off and legal review, per this project's standing rule.
  • GET /publishers/editions/{edition_id}/epub · scope publishers:readGenerate Epub
  • GET /publishers/editions/{edition_id}/isbn-assignment · scope publishers:readGet Isbn Assignment
  • POST /publishers/editions/{edition_id}/isbn-assignment · scope publishers:writeAssign Isbn
  • GET /publishers/editions/{edition_id}/marketplace-listing · scope publishers:readGet Marketplace Listing
  • POST /publishers/editions/{edition_id}/marketplace-listing · scope publishers:writeList Edition On MarketplaceOne real action: lists a finished Publishers edition as a real Marketplace product with the actual real EPUB/M4B as its digital- delivery file. Auto-provisions a real Medusa vendor identity for the publisher house's own organization on first use (get_or_provision_vendor) - a house never has to separately sign up as a Marketplace vendor first. Re-listing an already-listed edition UPDATES the same r…
  • GET /publishers/editions/{edition_id}/metadata · scope publishers:readGet Metadata
  • PUT /publishers/editions/{edition_id}/metadata · scope publishers:writeUpsert Metadata
  • GET /publishers/editions/{edition_id}/pod-asset · scope publishers:readGet Pod Asset
  • POST /publishers/editions/{edition_id}/pod-asset · scope publishers:writeCreate Pod Asset
  • GET /publishers/editions/{edition_id}/pod-print-jobs · scope publishers:readList Pod Print Jobs
  • POST /publishers/editions/{edition_id}/pod-print-jobs · scope publishers:writeCreate Pod Print Job
  • POST /publishers/editions/{edition_id}/polish-blurb · scope publishers:writePolish BlurbAI copy-edit assistance for the back-cover sales blurb (the same real gap the bio-polish endpoint closes above) - covers/illustrations already get AI drafting by default in this pillar, but the human-typed blurb text had none. Same "AI drafts, never authoritative" shape: the caller sends whatever's currently in the description field (saved or not), the stored metadata_record.description is never t…
  • GET /publishers/editions/{edition_id}/print-pdf · scope publishers:readGenerate Print Pdf
  • POST /publishers/editions/{edition_id}/regenerate-back-cover · scope publishers:writeRegenerate Auto Back CoverForces a genuinely fresh auto-composed back cover, same deliberate- override contract as regenerate_auto_cover above.
  • POST /publishers/editions/{edition_id}/regenerate-cover · scope publishers:writeRegenerate Auto CoverForces a genuinely fresh AI-generated cover, deliberately overwriting whatever is currently cached (or even a prior manual upload - an explicit Regenerate click is a real, intentional override, matching how upload_cover already overwrites a prior generated one). Old cached file deleted first via the same helper's own cleanup, same discipline as every other image-replacement endpoint in this pillar…
  • PATCH /publishers/editions/{edition_id}/restore · scope publishers:writeRestore Edition
  • GET /publishers/editions/{edition_id}/submission-package · scope publishers:readGenerate Submission PackageBundles the EPUB, print PDF, cover (manual if uploaded, else the real auto-generated one), and a metadata JSON into one zip - ready for a human to manually upload to Amazon KDP/IngramSpark/etc. Deliberately NOT an API auto-publish integration - see the Distribution Center's own docstring for why real auto-publishing stays out of Core.
  • GET /publishers/editions/{edition_id}/wraparound-cover · scope publishers:readGenerate Wraparound CoverA real, single continuous print-ready wraparound cover (back cover | spine | front cover) at this edition's own real per-format trim size (matching the actual print-interior PDF's own real trim for its format - paperback 5.5x8.5in, hardcover/large_print 6x9in) + bleed + 300 DPI. Requires the print-interior PDF to have been generated at least once already, since the spine width is computed from the…
  • POST /publishers/figure-briefs/{brief_id}/status · scope publishers:writeSet Figure Status
  • POST /publishers/houses · scope publishers:writeCreate House
  • GET /publishers/houses/mine · scope publishers:readGet My House Endpoint
  • PATCH /publishers/houses/{house_id} · scope publishers:writeUpdate HouseReal, minimal house settings update - currently just country_code, which real ISBN registration agencies key off of (a publisher must apply to the agency that operates where they're based, not any agency they choose - see the ISBN system's own real administration rule).
  • GET /publishers/houses/{house_id}/allocations · scope publishers:readList Allocations
  • PUT /publishers/houses/{house_id}/allocations · scope publishers:writeSet AllocationAny real house member can set another member's allocation cap - matches this pillar's own established collapsed-role convention (any member can act; owner-role override only where the spec actually names a distinct authority). The platform OWNER role's own spending is never subject to any cap regardless of what's recorded here for them.
  • PATCH /publishers/houses/{house_id}/archive · scope publishers:writeArchive HouseNo DELETE endpoint exists for a house at all - see this module's own docstring for why (every real table in this pillar cascades from publisher_house_id, so a hard delete could never be meaningfully blocked - archive is the only safe path).
  • GET /publishers/houses/{house_id}/authors · scope publishers:readList Authors
  • POST /publishers/houses/{house_id}/authors · scope publishers:writeCreate AuthorAuthor registry with synthetic data (spec section 4 item 3): an author profile is real data owned by a real logged-in Fitinty user, but nothing requires legal-identity verification. link_my_account attaches the caller's own user_id (the common "I am the author" case); leaving it false creates a placeholder co-author/pen-name entry with no linked account, which the spec's own "synthetic data" frami…
  • GET /publishers/houses/{house_id}/books · scope publishers:readList Books
  • POST /publishers/houses/{house_id}/books · scope publishers:writeCreate BookA book can only be created from a manuscript already at status='approved' (spec section 17's pipeline starts at "approved manuscript") - checked here, not just implied by UI ordering.
  • POST /publishers/houses/{house_id}/cart/total · scope publishers:writeCompute Cart Total
  • POST /publishers/houses/{house_id}/checkout · scope publishers:writeCheckout CartSpends the house's real fuel pool for a real cart. Enforces (a) sufficient house balance, (b) the caller's own real per-user allocation cap, if one has been set for them. The platform OWNER role is always exempt from any allocation cap (the roadmap's own 'Owner exemption') - everyone else's cap, if they have one, is enforced honestly against their own recorded real spend.
  • GET /publishers/houses/{house_id}/copyright-deposit-alerts · scope publishers:readList Copyright Deposit Alerts
  • GET /publishers/houses/{house_id}/fuel-account · scope publishers:readGet Fuel AccountGet-or-create the house's real fuel pool. A house starts at 0 credits until it subscribes to a tier - there is no free grant here, unlike a person's own Pioneer grant in Tools, since a house choosing not to subscribe is a real, valid state.
  • GET /publishers/houses/{house_id}/fuel-ledger · scope publishers:readGet Fuel Ledger
  • GET /publishers/houses/{house_id}/imprints · scope publishers:readList Imprints
  • POST /publishers/houses/{house_id}/imprints · scope publishers:writeCreate Imprint
  • GET /publishers/houses/{house_id}/isbn-alert-rules · scope publishers:readList Isbn Alert Rules
  • POST /publishers/houses/{house_id}/isbn-alert-rules · scope publishers:writeCreate Isbn Alert Rule
  • GET /publishers/houses/{house_id}/isbn-alerts · scope publishers:readList Isbn Alerts
  • GET /publishers/houses/{house_id}/isbn-inventory · scope publishers:readList Isbn Inventory
  • POST /publishers/houses/{house_id}/isbn-inventory · scope publishers:writeCreate Isbn Inventory
  • POST /publishers/houses/{house_id}/isbn-inventory/import-real · scope publishers:writeImport Real IsbnsImports real ISBN values the author already bought directly from a real agency (never fabricated, never resold by Fitinty - see migration 177's own comment for why). Every value is validated for real ISBN-13 shape/check-digit correctness and deduped against every ISBN already assigned or pooled anywhere on the platform before landing in a fresh real_import batch.
  • GET /publishers/houses/{house_id}/lccn-requests · scope publishers:readList Lccn Requests
  • POST /publishers/houses/{house_id}/logo · scope publishers:writeUpload House LogoReal publisher/imprint logo upload - previously zero logo/branding anywhere in the book pipeline. Same enhance_image() + local-disk pattern as upload_author_photo above, scoped to house-membership rather than author-house lookup.
  • GET /publishers/houses/{house_id}/logo · scope publishers:readGet House Logo
  • GET /publishers/houses/{house_id}/manuscripts · scope publishers:readList Manuscripts
  • POST /publishers/houses/{house_id}/manuscripts · scope publishers:writeCreate Manuscript
  • POST /publishers/houses/{house_id}/manuscripts/generate · scope publishers:writeGenerate ManuscriptProduce a manuscript from ideas via AI (spec section 1). The AI drafts a title/synopsis/chapter outline from the author's own notes - it never invents unrelated plot beyond what the notes support - and the result lands at status='idea', the earliest lifecycle stage, so nothing is treated as final until a human edits it through the same review-gated workflow every manuscript goes through.
  • POST /publishers/houses/{house_id}/manuscripts/import · scope publishers:writeImport ManuscriptManuscript import (spec section 1: "Manuscript creation and import"). Parses .txt/.md/.docx/.epub into real chapters, never just stores the raw file - the author lands straight in a usable editor. genre/series_bible_id (Phase F of the audit-fix pass) match what the blank-create path (create_manuscript's own ManuscriptIn) already accepts - previously an import required an immediate follow-up PATCH…
  • GET /publishers/houses/{house_id}/members · scope publishers:readList House Members
  • GET /publishers/houses/{house_id}/provenance · scope publishers:readList Provenance
  • POST /publishers/houses/{house_id}/provenance · scope publishers:writeCreate Provenance
  • PATCH /publishers/houses/{house_id}/restore · scope publishers:writeRestore House
  • GET /publishers/houses/{house_id}/rights-records · scope publishers:readList Rights Records
  • POST /publishers/houses/{house_id}/rights-records · scope publishers:writeCreate Rights Record
  • POST /publishers/houses/{house_id}/royalty-statements · scope publishers:writeGenerate StatementDraft = regenerated wholesale on every run. Final = refused (history).
  • GET /publishers/houses/{house_id}/royalty-statements · scope publishers:readList Statements
  • GET /publishers/houses/{house_id}/search · scope publishers:readSearchReal Postgres full-text search (tsvector/GIN), ranked, across manuscripts, chapters, Series/World Bibles, books, authors, and citations - not a placeholder LIKE query. Permission is applied before ranking: RLS already scopes every table to house members, and this additionally filters by house_id explicitly. book/author_profile/reference_entry (Phase F of the audit-fix pass, migration 189) close t…
  • GET /publishers/houses/{house_id}/series-bibles · scope publishers:readList Series Bibles
  • POST /publishers/houses/{house_id}/series-bibles · scope publishers:writeCreate Series Bible
  • POST /publishers/houses/{house_id}/subscribe · scope publishers:writeSubscribeSubscribes the house to a real tier and grants its bundled credits immediately - a real, honest SANDBOX grant regardless of bank_payments.is_live_enabled()'s state. Never charges the house's real payment method (none is even wired here).
  • GET /publishers/houses/{house_id}/voice-consent-records · scope publishers:readList Consent Records
  • POST /publishers/houses/{house_id}/voice-consent-records · scope publishers:writeCreate Consent RecordReal consent capture (spec section 18) - typed full legal name + the exact statement agreed to, timestamped, plus an optional uploaded signed document for genuine written authorization. This is the gate itself, built ahead of any actual voice-synthesis engine: nothing in this platform reads this table to authorize synthesis yet, because that engine doesn't exist - this makes the consent mechanism …
  • GET /publishers/houses/{house_id}/writing-voice-profiles · scope publishers:readList Voice Profiles
  • POST /publishers/houses/{house_id}/writing-voice-profiles · scope publishers:writeCreate Voice Profile
  • DELETE /publishers/imprints/{imprint_id} · scope publishers:writeDelete Imprint
  • PATCH /publishers/imprints/{imprint_id}/archive · scope publishers:writeArchive Imprint
  • GET /publishers/imprints/{imprint_id}/can-delete · scope publishers:readCan Delete Imprint
  • PATCH /publishers/imprints/{imprint_id}/restore · scope publishers:writeRestore Imprint
  • GET /publishers/integrations · scope publishers:readList IntegrationsHonest status per spec section 7's candidate list - reports what's genuinely wired into this codebase vs. what's a named candidate still needing its own license/security/sandbox review, same discipline as every other pillar's integration registry (Games, Marketplace, Education).
  • GET /publishers/isbn-agencies · scope publishers:readList Isbn Agencies
  • POST /publishers/isbn-agencies · scope publishers:writeCreate Isbn Agency
  • PATCH /publishers/isbn-agencies/{agency_id} · scope publishers:writeUpdate Isbn Agency
  • POST /publishers/isbn-agencies/{agency_id}/tiers · scope publishers:writeCreate Isbn Agency Tier
  • PATCH /publishers/isbn-agency-tiers/{tier_id} · scope publishers:writeUpdate Isbn Agency Tier
  • PATCH /publishers/isbn-alert-rules/{rule_id} · scope publishers:writeUpdate Isbn Alert RuleReal edit-after-creation for an alert rule - previously only ever creatable, never editable (a house wanting a different threshold, or to pause/resume the rule without deleting its history, had no way to do either).
  • POST /publishers/isbn-alerts/{alert_id}/acknowledge · scope publishers:writeAcknowledge Isbn Alert
  • PATCH /publishers/lccn-requests/{request_id} · scope publishers:writeUpdate Lccn Request
  • GET /publishers/length-standards · scope publishers:readLength Standards
  • POST /publishers/manuscript-versions/{version_id}/restore · scope publishers:writeRestore Manuscript VersionReal restore-to-a-prior-version. First snapshots the CURRENT chapter state as a brand new version (so the restore is itself reversible - see _snapshot_manuscript_version above), then applies the target version's own stored per-chapter title/content/position back onto the real `chapter` rows, matched by each chapter's own real id. Deliberately conservative, not a destructive full rollback: a chapt…
  • GET /publishers/manuscripts/{manuscript_id} · scope publishers:readGet Manuscript
  • PATCH /publishers/manuscripts/{manuscript_id} · scope publishers:writeUpdate Manuscript
  • DELETE /publishers/manuscripts/{manuscript_id} · scope publishers:writeDelete Manuscript
  • PATCH /publishers/manuscripts/{manuscript_id}/archive · scope publishers:writeArchive Manuscript
  • POST /publishers/manuscripts/{manuscript_id}/book-type · scope publishers:writeSet Book Type
  • POST /publishers/manuscripts/{manuscript_id}/build · scope publishers:writeStart BuildQueue the long-form build. Refuses without a book type (no band, no honest target) and refuses to double-run.
  • GET /publishers/manuscripts/{manuscript_id}/build-jobs · scope publishers:readBuild Jobs
  • GET /publishers/manuscripts/{manuscript_id}/can-delete · scope publishers:readCan Delete Manuscript
  • GET /publishers/manuscripts/{manuscript_id}/chapters · scope publishers:readList Chapters
  • POST /publishers/manuscripts/{manuscript_id}/chapters · scope publishers:writeCreate Chapter
  • POST /publishers/manuscripts/{manuscript_id}/chapters/reorder · scope publishers:writeReorder ChaptersReal chapter reordering (row 9) - the corkboard view's own drag-drop action. body.chapter_ids must be EXACTLY the manuscript's current real chapter id set (same count, same ids, any order) before anything is applied - a partial or stale list (e.g. a chapter deleted by someone else mid-drag) is rejected outright rather than silently reordering only some chapters and leaving the rest at ambiguous po…
  • GET /publishers/manuscripts/{manuscript_id}/comments · scope publishers:readList Comments
  • POST /publishers/manuscripts/{manuscript_id}/comments · scope publishers:writeCreate Comment
  • GET /publishers/manuscripts/{manuscript_id}/competencies · scope publishers:readList CompetenciesRLS alone scopes this to houses the caller is actually a member of - the same reliance list_references already places on RLS for this exact class of manuscript-scoped child resource.
  • POST /publishers/manuscripts/{manuscript_id}/completeness-check · scope publishers:writeRun Completeness CheckReal AI completeness pass (row 9, item 5) - reads the manuscript against its OWN stated scope (title/genre/synopsis) and flags real gaps a human should look at. A draft suggestion for the author to act on, never an automatic edit - this never writes to chapter/manuscript content itself, only returns findings.
  • POST /publishers/manuscripts/{manuscript_id}/course-conversion · scope publishers:writePropose Course ConversionBook-to-Course bridge, step 1 (spec section 19): AI proposes a chapter -> module -> lesson -> learning-objective mapping. Nothing is written to Education yet - this only creates a reviewable course_conversion_item, matching "Global Push must create a reviewable change set, not overwrite approved courses automatically".
  • GET /publishers/manuscripts/{manuscript_id}/course-conversion · scope publishers:readList Course Conversions
  • POST /publishers/manuscripts/{manuscript_id}/edit-pass · scope publishers:writeStart Edit PassOne stage of the rail as a queued pass. Suggestions ride the SAME polish table and doors a person's own polish run uses - never a parallel pile; the pass files a real review_cycle row so the manuscript's approval gate sees the work.
  • POST /publishers/manuscripts/{manuscript_id}/extract-competencies · scope publishers:writeExtract CompetenciesReal AI-assisted extraction (task #162) - grounded strictly in the manuscript's own real chapter text, never invented, mirroring Games' already-proven extracted_entity pattern. Every extracted item lands at canon_state='proposed' - nothing is ever treated as real/actionable until a human explicitly approves it via the review endpoint below. This is the extraction/foundation half of the standing "d…
  • GET /publishers/manuscripts/{manuscript_id}/figure-briefs · scope publishers:readFigure Briefs
  • GET /publishers/manuscripts/{manuscript_id}/references · scope publishers:readList ReferencesTask #124's real reference library - an author's bibliography entries for this manuscript, orderable by whichever ones are actually cited via [[cite:key]] (see collect_citation_order in publishers_content_blocks.py). No explicit access check here, same as list_chapters - reference_entry's own RLS policy already scopes this to real house members.
  • POST /publishers/manuscripts/{manuscript_id}/references · scope publishers:writeCreate Reference
  • PATCH /publishers/manuscripts/{manuscript_id}/restore · scope publishers:writeRestore Manuscript
  • GET /publishers/manuscripts/{manuscript_id}/review-cycles · scope publishers:readList Review Cycles
  • POST /publishers/manuscripts/{manuscript_id}/review-cycles · scope publishers:writeCreate Review Cycle
  • GET /publishers/manuscripts/{manuscript_id}/thoroughness · scope publishers:readThoroughnessModel-free: real words against the type's band, the verdict in WORDS. A manuscript with no type gets its counts and a sentence, never a made-up target.
  • GET /publishers/manuscripts/{manuscript_id}/translation-siblings · scope publishers:readList Translation SiblingsEvery real manuscript in this one's translation family - the original plus every other real materialized translated sibling - so a reader/editor can navigate to any language version from any other one. Resolves through the real original regardless of which family member manuscript_id points at (a sibling's own translated_from_manuscript_id, or its own id if it IS the original). Returns an empty li…
  • POST /publishers/manuscripts/{manuscript_id}/translations · scope publishers:writeCreate Translation
  • GET /publishers/manuscripts/{manuscript_id}/translations · scope publishers:readList Translations
  • POST /publishers/manuscripts/{manuscript_id}/translations/bulk · scope publishers:writeCreate Translations BulkMulti-locale translation in one request - an author picks several target languages from Fitinty's own LANGUAGES registry instead of requesting one locale at a time. Reuses create_translation's own real per-locale insert/dedup logic (the same ON CONFLICT DO NOTHING check, so a locale that already has a translation is reported, never silently duplicated or re-queued) rather than a separate mechanism…
  • GET /publishers/manuscripts/{manuscript_id}/versions · scope publishers:readList Manuscript Versions
  • POST /publishers/manuscripts/{manuscript_id}/versions · scope publishers:writeSave Manuscript VersionSnapshots every current chapter's title/content/position into one manuscript_version row - real, reviewable revision history without a per-chapter version table.
  • GET /publishers/narrator-voice-options · scope publishers:readList Narrator Voice Options
  • DELETE /publishers/plan-items/{item_id} · scope publishers:writeRemove ItemOnly a still-'planned' promise may be withdrawn - in-progress and kept ones are history.
  • POST /publishers/plan-items/{item_id}/link · scope publishers:writeLink ItemA kept promise names its proof - the human hand links what reconciliation cannot prove from tables (movies, games, trailers, campaigns).
  • POST /publishers/plan-items/{item_id}/status · scope publishers:writeSet Item Status
  • PATCH /publishers/pod-assets/{pod_id} · scope publishers:writeUpdate Pod Asset
  • POST /publishers/pod-print-jobs/{job_id}/refresh · scope publishers:writeRefresh Pod Print JobRe-polls the provider for the job's current real status - stored verbatim, never normalized into locally-invented states.
  • GET /publishers/pod-print/artifacts/{edition_id}/{kind} · scope publishers:readServe Signed ArtifactPublic (no-session) artifact route for the POD providers' own file fetchers. Access is not anonymous in the RLS sense: the HMAC-signed URL embeds the submitting user's id, and the artifact is assembled under that exact user's normal RLS context - the URL grants precisely the access its creator already had, time-limited, for these two artifacts only.
  • GET /publishers/pod-print/providers · scope publishers:readProvider Status
  • POST /publishers/polish-suggestions/{suggestion_id}/apply · scope publishers:writeApply Suggestion
  • POST /publishers/polish-suggestions/{suggestion_id}/dismiss · scope publishers:writeDismiss Suggestion
  • PATCH /publishers/references/{reference_id} · scope publishers:writeUpdate Reference
  • DELETE /publishers/references/{reference_id} · scope publishers:writeDelete Reference
  • PATCH /publishers/review-cycles/{review_id} · scope publishers:writeUpdate Review Cycle
  • PATCH /publishers/rights-records/{record_id} · scope publishers:writeUpdate Rights Record
  • PATCH /publishers/rights-records/{record_id}/copyright-details · scope publishers:writeUpdate Copyright DetailsA real, human-entered record of the author's own progress through the Copyright Office's eCO portal - registration_number/registration_filed_at once they receive their real certificate back, first_published_at as the real legal anchor for the mandatory-deposit deadline, deposit_method/ deposit_completed_at once they've actually deposited a copy. Never fabricates, files, or submits anything on the …
  • GET /publishers/rights-records/{record_id}/document · scope publishers:readDownload Rights Document
  • POST /publishers/royalty-lines/{line_id}/record-payment · scope publishers:writeRecord Line PaymentThe handshake record: the house paid the author by its own hand (bank, cash, cheque) and writes down how. Nothing here moves money - Rule 3 is the wall.
  • DELETE /publishers/royalty-splits/{split_id} · scope publishers:writeRemove Split
  • GET /publishers/royalty-statements/{statement_id} · scope publishers:readStatement Detail
  • DELETE /publishers/royalty-statements/{statement_id} · scope publishers:writeVoid StatementThe escape from a trapped period: a DRAFT voids freely (it was a reading), and a FINAL statement voids ONLY when it holds nothing - no lines, so nothing was ever owed on it. A final statement with lines is history and never deletes.
  • POST /publishers/royalty-statements/{statement_id}/finalize · scope publishers:writeFinalize Statement
  • POST /publishers/series-bible-versions/{version_id}/restore · scope publishers:writeRestore Series Bible VersionReal restore-to-a-prior-version - the actual point of keeping version history at all, not just a read-only log. Restoring itself FIRST snapshots the current (about-to-be-overwritten) state as a brand new version, so a restore is itself reversible - the same "always snapshot before mutating" discipline update_series_bible already uses, applied one level up. The target snapshot's own immutable/manag…
  • GET /publishers/series-bibles/{bible_id} · scope publishers:readGet Series Bible
  • PATCH /publishers/series-bibles/{bible_id} · scope publishers:writeUpdate Series BibleEvery save snapshots the full prior state into series_bible_version before applying the update - real "version control" (spec section 10) without a per-field diff/merge engine. Snapshots the state BEFORE the change so version N always reads as "what it looked like going into change N+1".
  • DELETE /publishers/series-bibles/{bible_id} · scope publishers:writeDelete Series Bible
  • PATCH /publishers/series-bibles/{bible_id}/archive · scope publishers:writeArchive Series Bible
  • GET /publishers/series-bibles/{bible_id}/books · scope publishers:readList Series Books
  • GET /publishers/series-bibles/{bible_id}/can-delete · scope publishers:readCan Delete Series Bible
  • POST /publishers/series-bibles/{bible_id}/plan/declare · scope publishers:writeDeclare Series PlanDeclared once at the series, MATERIALIZED onto every book in it - each volume carries and fulfills its own copy; a book that joins the series later gets the plan by running this again (adds only what's missing, per book).
  • POST /publishers/series-bibles/{bible_id}/program-conversion · scope publishers:writePropose Series ProgramSeries -> Education Program bridge, step 1. Real per-book chapter/ module/lesson mapping stays entirely on the existing, unmodified propose_course_conversion/review_course_conversion/push_course_conversion flow - this only creates the program-level shell (title/description drafted from the series bible's own real fields, grounded, never invented) and, for every not-yet-linked book in the series, a…
  • GET /publishers/series-bibles/{bible_id}/program-conversions · scope publishers:readList Series Program Conversions
  • PATCH /publishers/series-bibles/{bible_id}/restore · scope publishers:writeRestore Series Bible
  • GET /publishers/series-bibles/{bible_id}/versions · scope publishers:readList Series Bible Versions
  • PATCH /publishers/series-program-conversions/{item_id} · scope publishers:writeReview Series Program
  • DELETE /publishers/series-program-conversions/{item_id} · scope publishers:writeDiscard Series ProgramSame real discard rule as course_conversion_item above - a 'pushed' series program has a real education_program with real linked courses behind it, so it can never be deleted here.
  • POST /publishers/series-program-conversions/{item_id}/push · scope publishers:writePush Series ProgramSeries -> Education Program bridge, step 2: creates the REAL education_program shell (now that a target school is known) and links every book's course_conversion_item to it via program_id. Each course itself still goes through the existing, unmodified per-book review_course_conversion/push_course_conversion flow - THAT is what actually creates the real course/module/lesson rows and, thanks to the …
  • GET /publishers/service-catalog · scope publishers:readList Service Catalog
  • POST /publishers/service-catalog · scope publishers:writeCreate Service Catalog Item
  • GET /publishers/subscription-tiers · scope publishers:readList Subscription Tiers
  • POST /publishers/subscription-tiers · scope publishers:writeCreate Subscription Tier
  • DELETE /publishers/translations/{translation_id} · scope publishers:writeDelete Translation
  • GET /publishers/translations/{translation_id}/chapters · scope publishers:readList Translated Chapters
  • POST /publishers/translations/{translation_id}/create-manuscript · scope publishers:writeCreate Manuscript From TranslationMaterializes a real, human-reviewed translation into a genuine, independent `manuscript` row - a real sibling of the original, not just translated text sitting in manuscript_translation - so it flows through the exact same real editorial-review pipeline, book/edition creation, and EPUB/print-PDF generation every other manuscript already uses (see the translation-notice wiring in publishers_product…
  • PATCH /publishers/translations/{translation_id}/mark-reviewed · scope publishers:writeMark ReviewedA human explicitly confirming the MT draft has been reviewed - never automatic, matching this project's standing 'AI drafts, never authoritative' pattern. Now requires a real, completed review_cycle approval first (task #123) - the same "real gate, not a cosmetic button" discipline already used for the manuscript's own production-status gate (publishers_manuscripts.py's GATED_STATUS check) - a tra…
  • GET /publishers/translations/{translation_id}/review-cycles · scope publishers:readList Translation Review Cycles
  • POST /publishers/translations/{translation_id}/review-cycles · scope publishers:writeCreate Translation Review CycleRequests a real, structured editorial review against a translation DRAFT - the same review_cycle machinery a manuscript uses, reused rather than duplicated (see migration 173). Only valid once the real MT draft actually exists (status='draft_ready' or later - requesting a review against a still-generating or failed translation makes no sense, since there's nothing real yet to review).
  • GET /publishers/voice-calibration-scripts · scope publishers:readList Calibration Scripts
  • GET /publishers/voice-consent-records/{record_id}/document · scope publishers:readDownload Consent Document
  • PATCH /publishers/voice-consent-records/{record_id}/revoke · scope publishers:writeRevoke Consent Record
  • GET /publishers/voice-consent-records/{record_id}/samples · scope publishers:readList Voice Samples
  • POST /publishers/voice-consent-records/{record_id}/samples · scope publishers:writeUpload Voice SampleA real microphone recording of the person reading a calibration passage - captured with the browser's own mic permission prompt, never silently. Can ONLY be attached to a consent record that is still ACTIVE (checked here, not just assumed from the UI) - the actual mechanical enforcement of "no voice clone without consent," since nothing downstream can ever have a sample without a live consent reco…
  • POST /publishers/voice-consent-records/{record_id}/train-voice-lora · scope publishers:writeTrain Voice LoraReal per-voice LoRA training (VoxCPM2) against every real captured sample for this consent record - the exact same mechanism already proven end-to-end for Cinema, now available to Publishers' own real, previously- unused consent samples. Blocks synchronously while training runs (real GPU time, can take several minutes) - matching cinema_identity.py's own train_voice_lora precedent for this exact e…
  • DELETE /publishers/voice-samples/{sample_id} · scope publishers:writeDelete Voice Sample
  • GET /publishers/voice-samples/{sample_id}/file · scope publishers:readGet Voice Sample File
  • PATCH /publishers/writing-voice-profiles/{profile_id} · scope publishers:writeUpdate Voice Profile
  • POST /publishers/writing-voice-profiles/{profile_id}/draft · scope publishers:writeDraft With VoiceAI drafting assistance (spec section 11): labeled suggestion, never saved automatically, never overwrites existing text - the caller pastes the returned draft into the manuscript editor themselves.
Workspace - 425 doors - workspace:read, workspace:write

The tools every tenant works in - documents, mail, calendar, forms - and the ones sold direct to customers.

  • GET /bi/dashboards · scope workspace:readList Dashboards
  • POST /bi/dashboards · scope workspace:writeCreate Dashboard
  • GET /bi/dashboards/{dashboard_id} · scope workspace:readGet Dashboard
  • PATCH /bi/dashboards/{dashboard_id} · scope workspace:writeUpdate Dashboard
  • DELETE /bi/dashboards/{dashboard_id} · scope workspace:writeDelete Dashboard
  • GET /bi/dashboards/{dashboard_id}/values · scope workspace:readGet Dashboard Values
  • POST /bi/dashboards/{dashboard_id}/widgets · scope workspace:writeCreate Widget
  • GET /bi/dashboards/{dashboard_id}/widgets · scope workspace:readList Widgets
  • GET /bi/metrics · scope workspace:readList Metrics
  • GET /bi/my-organizations · scope workspace:readMy Organizations
  • PATCH /bi/widgets/{widget_id} · scope workspace:writeUpdate Widget
  • DELETE /bi/widgets/{widget_id} · scope workspace:writeDelete Widget
  • GET /bookkeeping/bills · scope workspace:readList Bills
  • POST /bookkeeping/bills · scope workspace:writeCreate Bill
  • DELETE /bookkeeping/bills/{bill_id} · scope workspace:writeDelete Bill
  • POST /bookkeeping/bills/{bill_id}/status · scope workspace:writeUpdate Bill Status
  • GET /bookkeeping/invoices · scope workspace:readList Invoices
  • POST /bookkeeping/invoices · scope workspace:writeCreate Invoice
  • DELETE /bookkeeping/invoices/{invoice_id} · scope workspace:writeDelete Invoice
  • POST /bookkeeping/invoices/{invoice_id}/status · scope workspace:writeUpdate Invoice Status
  • GET /bookkeeping/my-organizations · scope workspace:readMy Organizations
  • GET /bookkeeping/summary/{organization_id} · scope workspace:readSummary
  • GET /brief/{organization_id}/latest · scope workspace:readLatest Brief
  • POST /brief/{organization_id}/write · scope workspace:writeWrite Brief
  • POST /community/announcements · scope workspace:writeCreate Announcement
  • GET /community/announcements · scope workspace:readMy Announcements
  • POST /community/announcements/{announcement_id}/publish · scope workspace:writePublish Announcement
  • POST /community/clubs · scope workspace:writeCreate Club
  • POST /community/clubs/{club_id}/join · scope workspace:writeJoin ClubAny signed-in person may join - the open-room design. The RLS WITH CHECK (you join AS YOURSELF) raises, so the polite sentences come first. FM10 (618): a share_room opens only to a holder of its plan, a growers_exchange only to someone on a farm's own membership - both asked on the caller's cursor, both refused in words.
  • DELETE /community/clubs/{club_id}/join · scope workspace:writeLeave Club
  • POST /community/clubs/{club_id}/posts · scope workspace:writePost To ClubMembers write as themselves. The membership sentence comes BEFORE the insert - the RLS WITH CHECK raises, never empty-returns (the EC1 lesson).
  • POST /community/posts/{post_id}/hide · scope workspace:writeHide PostThe moderator's hand: hides with a REASON, never erases.
  • GET /content-calendar/entries · scope workspace:readList Entries
  • POST /content-calendar/entries · scope workspace:writeCreate Entry
  • PATCH /content-calendar/entries/{entry_id} · scope workspace:writeUpdate Entry
  • DELETE /content-calendar/entries/{entry_id} · scope workspace:writeDelete Entry
  • POST /content-calendar/entries/{entry_id}/draft · scope workspace:writeDraft Entry
  • POST /content-calendar/entries/{entry_id}/mark-posted · scope workspace:writeMark PostedThe tenant records that THEY posted this manually. The platform never posts for them - that is the standing real-communications gate, and it holds here.
  • POST /content-calendar/entries/{entry_id}/status · scope workspace:writeSet StatusMoves an entry between planning states. 'posted' is deliberately NOT accepted here - it only exists via /mark-posted, which records the tenant's own manual action.
  • POST /content-calendar/plan-month · scope workspace:writePlan MonthFills a month with idea entries from a plain brief. Ideas land as 'idea' status - the tenant curates; nothing is drafted, let alone posted, on its own.
  • GET /content-calendar/shipped · scope workspace:readShippedReal dated output from the org's other pillars - published blog posts, simulated newsletter sends, published podcast episodes - so the plan sits next to reality.
  • POST /content-translations · scope workspace:writeDraft TranslationA machine draft into one locale. It waits for a person before any visitor can see it.
  • GET /content-translations/pending-review · scope workspace:readPending ReviewEvery machine draft this person may approve, the SOURCE beside each - a reviewer checks a translation against something, and it has to be on the same screen.
  • GET /content-translations/subjects · scope workspace:readMy SubjectsEverything the person's businesses show on their storefronts, with the translations each already has - the list a tenant translates FROM.
  • PUT /content-translations/{kind}/{subject_id}/{locale} · scope workspace:writeWrite TranslationA person's own translation: written = approved. Writes over a machine draft if one stands.
  • DELETE /content-translations/{translation_id} · scope workspace:writeDelete Translation
  • PATCH /content-translations/{translation_id}/mark-reviewed · scope workspace:writeMark ReviewedA person approving ONE machine draft - the only way a draft ever reaches a visitor.
  • POST /document-assist/compare · scope workspace:writeCreate Compare
  • GET /document-assist/documents · scope workspace:readList Documents
  • POST /document-assist/documents · scope workspace:writeUpload Document
  • DELETE /document-assist/documents/{document_id} · scope workspace:writeDelete Document
  • POST /document-assist/draft · scope workspace:writeCreate Draft
  • POST /document-assist/extract · scope workspace:writeCreate Extract
  • GET /document-assist/jobs · scope workspace:readList Jobs
  • GET /document-assist/jobs/{job_id}/download · scope workspace:readDownload Job
  • POST /document-assist/minutes · scope workspace:writeCreate Minutes
  • POST /document-assist/proofread · scope workspace:writeCreate Proofread
  • GET /document-assist/qa · scope workspace:readQa History
  • POST /document-assist/qa · scope workspace:writeAsk
  • GET /domain-monitor · scope workspace:readList Watched Domains
  • POST /domain-monitor · scope workspace:writeAdd Watched Domain
  • GET /domain-monitor/my-organizations · scope workspace:readMy Organizations
  • DELETE /domain-monitor/{watch_id} · scope workspace:writeRemove Watched Domain
  • POST /domain-monitor/{watch_id}/check · scope workspace:writeCheck Watched Domain
  • DELETE /field-inspection/items/{item_id} · scope workspace:writeDelete Template Item
  • GET /field-inspection/my-organizations · scope workspace:readMy Organizations
  • GET /field-inspection/runs · scope workspace:readList Runs
  • POST /field-inspection/runs · scope workspace:writeCreate Run
  • GET /field-inspection/runs/{run_id} · scope workspace:readGet Run
  • DELETE /field-inspection/runs/{run_id} · scope workspace:writeDelete Run
  • POST /field-inspection/runs/{run_id}/complete · scope workspace:writeComplete Run
  • GET /field-inspection/runs/{run_id}/responses · scope workspace:readList Responses
  • POST /field-inspection/runs/{run_id}/responses · scope workspace:writeRecord Response
  • GET /field-inspection/runs/{run_id}/summary · scope workspace:readRun Summary
  • GET /field-inspection/templates · scope workspace:readList Templates
  • POST /field-inspection/templates · scope workspace:writeCreate Template
  • GET /field-inspection/templates/{template_id} · scope workspace:readGet Template
  • DELETE /field-inspection/templates/{template_id} · scope workspace:writeDelete Template
  • POST /field-inspection/templates/{template_id}/items · scope workspace:writeAdd Template Item
  • GET /fitinchat/analytics · scope workspace:readAnalyticsComputed from real rows, nothing sampled or estimated: AI-resolved = closed without a human ever claiming it; escalated = an escalation reason was recorded.
  • GET /fitinchat/channels · scope workspace:readList Channels
  • POST /fitinchat/channels · scope workspace:writeCreate Channel
  • PATCH /fitinchat/channels/{channel_id} · scope workspace:writeUpdate Channel
  • GET /fitinchat/conversations · scope workspace:readList Conversations
  • POST /fitinchat/conversations/{conversation_id}/claim · scope workspace:writeClaim Conversation
  • POST /fitinchat/conversations/{conversation_id}/close · scope workspace:writeClose Conversation
  • GET /fitinchat/conversations/{conversation_id}/messages · scope workspace:readList Conversation Messages
  • POST /fitinchat/conversations/{conversation_id}/reply · scope workspace:writeStaff Reply
  • POST /fitinchat/kb-reindex · scope workspace:writeKb Reindex
  • GET /fitinchat/my-organizations · scope workspace:readMy Organizations
  • GET /fitinchat/site/{slug}/channel · scope workspace:readStorefront ChannelThe storefront's way in: the org's 'Storefront' channel, created lazily the first time a live storefront asks. Live-theme gated like every public window. Landing here means every visitor chat is a REAL fitinchat conversation the tenant's team sees, counts in 'unanswered messages', and can take over - the AI is the first responder, never a separate machine.
  • GET /fitinchat/widget/conversations/{visitor_token} · scope workspace:readWidget Conversation Status
  • GET /fitinchat/widget/conversations/{visitor_token}/messages · scope workspace:readWidget List Messages
  • POST /fitinchat/widget/conversations/{visitor_token}/messages · scope workspace:writeWidget Send Message
  • POST /fitinchat/widget/conversations/{visitor_token}/order-lookup · scope workspace:writeWidget Order LookupThe widget's explicit "Look up my order" form. Same trust model as the AI action (fitinchat_intelligence.lookup_order): order number + email must both match the REAL Medusa order and the order must contain this shop's products; the visible result is a public-safe summary posted into the conversation as an assistant message (so staff who later claim the chat see exactly what the visitor saw). Not-f…
  • POST /fitinchat/widget/conversations/{visitor_token}/rate · scope workspace:writeWidget RatePost-conversation satisfaction, 1-5, only once and only after close - the widget shows the prompt when the conversation ends.
  • POST /fitinchat/widget/conversations/{visitor_token}/request-human · scope workspace:writeWidget Request Human
  • GET /fitinchat/widget/{channel_id} · scope workspace:readWidget Channel View
  • POST /fitinchat/widget/{channel_id}/start · scope workspace:writeStart Conversation
  • GET /fitincrm/book/{organization_id} · scope workspace:readRead BookThe one book. Segments are READERS over recorded touches, never stored lists - the same person appears in 'regular' and 'consented' at once because segments are views, not silos.
  • GET /fitincrm/book/{organization_id}/advice · scope workspace:readSecond Sale AdviceCALL THESE FIVE TODAY. Deterministic rules over the recorded book - every line carries its evidence in words and a proposed_action, and NO MODEL writes any of it (the same contract as analytics_advice: advice is a reader, drafting is the only place AI touches).
  • GET /fitincrm/book/{organization_id}/contacts/{contact_id} · scope workspace:readRead Contact Page
  • POST /fitincrm/book/{organization_id}/contacts/{contact_id}/consent · scope workspace:writeRecord ConsentAppends to the consent ledger. The ORIGIN is required in words - 'she checked the box', 'he asked at the counter today' - because a yes nobody can place is a yes nobody can defend.
  • POST /fitincrm/book/{organization_id}/contacts/{contact_id}/draft-winback · scope workspace:writeDraft WinbackAI DRAFTS, NEVER SENDS. The draft is grounded in this contact's real recorded moments and the org's identity-pack voice, and it is returned to the human to send by their own hand - nothing here transmits, so the mail rail's gate is not even in the path. CONSENT BY CONSTRUCTION: a marketing message to someone with no recorded 'granted' row is refused before any model runs - the machine cannot draf…
  • GET /fitincrm/contacts · scope workspace:readList Contacts
  • POST /fitincrm/contacts · scope workspace:writeCreate Contact
  • DELETE /fitincrm/contacts/{contact_id} · scope workspace:writeDelete Contact
  • GET /fitincrm/deals · scope workspace:readList Deals
  • POST /fitincrm/deals · scope workspace:writeCreate Deal
  • PATCH /fitincrm/deals/{deal_id} · scope workspace:writeUpdate Deal
  • GET /fitincrm/deals/{deal_id}/activities · scope workspace:readList Activities
  • POST /fitincrm/deals/{deal_id}/activities · scope workspace:writeCreate Activity
  • POST /fitincrm/deals/{deal_id}/stage · scope workspace:writeMove Deal Stage
  • GET /fitincrm/my-organizations · scope workspace:readMy Organizations
  • GET /fitincrm/pipeline-summary/{organization_id} · scope workspace:readPipeline Summary
  • GET /fitinevents/events · scope workspace:readList Events
  • POST /fitinevents/events · scope workspace:writeCreate Event
  • GET /fitinevents/events/{event_id} · scope workspace:readGet Event
  • PATCH /fitinevents/events/{event_id} · scope workspace:writeUpdate Event
  • DELETE /fitinevents/events/{event_id} · scope workspace:writeDelete Event
  • POST /fitinevents/events/{event_id}/cancel · scope workspace:writeCancel Event
  • POST /fitinevents/events/{event_id}/open-to-public · scope workspace:writeOpen Event To PublicMints the event's public slug. Publishing already made it real; this makes it REACHABLE - the door strangers register and pay through.
  • POST /fitinevents/events/{event_id}/publish · scope workspace:writePublish Event
  • GET /fitinevents/events/{event_id}/registrations · scope workspace:readList Registrations
  • POST /fitinevents/events/{event_id}/registrations · scope workspace:writeRegister Attendee
  • GET /fitinevents/my-organizations · scope workspace:readMy Organizations
  • GET /fitinevents/registrations/mine · scope workspace:readMy SeatsTHE PERSON'S OWN SEATS (2026-09-11, the customer Space's "Elsewhere"): every event they hold a seat at, across every business, with the ticket code that IS their confirmation. A seat names its person only by the attendee's email - `registered_by` is the staff hand or 'public-registration', never the attendee - so ownership is the standing rule (own_identity): read on the system cursor, decided her…
  • POST /fitinevents/registrations/{registration_id}/cancel · scope workspace:writeCancel Registration
  • POST /fitinevents/registrations/{ticket_code}/check-in · scope workspace:writeCheck In
  • GET /fitingive/donations · scope workspace:readList Donations
  • POST /fitingive/donations · scope workspace:writeCreate Donation
  • GET /fitingive/donations/mine · scope workspace:readMy Gifts
  • GET /fitingive/donations/mine/{donation_id}/receipt · scope workspace:readMy Gift ReceiptThe giver's own copy of the church's receipt - the SAME renderer, so the two cannot differ. A gift not theirs is 404 (existence is not theirs to learn); a gift with no receipt says why.
  • DELETE /fitingive/donations/{donation_id} · scope workspace:writeDelete Donation
  • GET /fitingive/donations/{donation_id}/receipt · scope workspace:readDonation ReceiptThe printable receipt. Facts from the ledger row alone - amount, date, fund, the church's own name - plus the sentence US-style acknowledgment letters carry. Rendered for the CHURCH to print or save and hand over; nothing is emailed here (the mail rail is closed by ruling until launch).
  • POST /fitingive/donations/{donation_id}/receipt-sent · scope workspace:writeMark Receipt Sent
  • GET /fitingive/donors · scope workspace:readList Donors
  • POST /fitingive/donors · scope workspace:writeCreate Donor
  • DELETE /fitingive/donors/{donor_id} · scope workspace:writeDelete Donor
  • GET /fitingive/funds · scope workspace:readList Funds
  • POST /fitingive/funds · scope workspace:writeCreate Fund
  • PATCH /fitingive/funds/{fund_id} · scope workspace:writeUpdate Fund
  • GET /fitingive/grants · scope workspace:readList Grants
  • POST /fitingive/grants · scope workspace:writeCreate Grant
  • DELETE /fitingive/grants/{grant_id} · scope workspace:writeDelete Grant
  • POST /fitingive/grants/{grant_id}/status · scope workspace:writeMove Grant Status
  • GET /fitingive/my-organizations · scope workspace:readMy Organizations
  • GET /fitingive/summary/{organization_id} · scope workspace:readSummary
  • GET /fitinkb/articles · scope workspace:readList Articles
  • POST /fitinkb/articles · scope workspace:writeCreate Article
  • GET /fitinkb/articles/{article_id} · scope workspace:readGet Article
  • PATCH /fitinkb/articles/{article_id} · scope workspace:writeUpdate Article
  • DELETE /fitinkb/articles/{article_id} · scope workspace:writeDelete Article
  • POST /fitinkb/articles/{article_id}/publish · scope workspace:writePublish Article
  • POST /fitinkb/articles/{article_id}/unpublish · scope workspace:writeUnpublish Article
  • GET /fitinkb/categories · scope workspace:readList Categories
  • POST /fitinkb/categories · scope workspace:writeCreate Category
  • PATCH /fitinkb/categories/{category_id} · scope workspace:writeUpdate Category
  • DELETE /fitinkb/categories/{category_id} · scope workspace:writeDelete Category
  • GET /fitinkb/my-organizations · scope workspace:readMy Organizations
  • GET /fitinkb/search · scope workspace:readSearch Articles
  • GET /fitinsign · scope workspace:readList Documents
  • POST /fitinsign · scope workspace:writeCreate Document
  • GET /fitinsign/my-organizations · scope workspace:readMy Organizations
  • GET /fitinsign/sign/{token} · scope workspace:readView Signing Link
  • POST /fitinsign/sign/{token} · scope workspace:writeSign Document
  • POST /fitinsign/sign/{token}/decline · scope workspace:writeDecline Document
  • GET /fitinsign/{document_id}/download · scope workspace:readDownload Document File
  • GET /fitinsign/{document_id}/events · scope workspace:readList Events
  • POST /fitinsign/{document_id}/send · scope workspace:writeSend Document
  • POST /fitinsign/{document_id}/upload · scope workspace:writeUpload Document File
  • POST /fitinsign/{document_id}/void · scope workspace:writeVoid Document
  • GET /hiring/applications · scope workspace:readList Applications
  • POST /hiring/applications/{application_id}/fit-summary · scope workspace:writeFit SummaryWORDS WITH EVIDENCE, never a score, never a decision - automated employment decisions are regulated territory (NYC LL144 and kin) and 'no model writes a decision' is this platform's own law. The summary quotes what the applicant SAID against what the post ASKS, names what is unknown, and the staff member decides. Runs on the staff member's own click (the /open precedent for a synchronous model cal…
  • POST /hiring/applications/{application_id}/hire · scope workspace:writeHire ApplicantThe rail's destination: one move mints ST1's employee record and the sign-in invite. The application keeps the whole story - who applied, when, and what they became.
  • POST /hiring/applications/{application_id}/interview · scope workspace:writeSchedule Interview
  • POST /hiring/applications/{application_id}/offer · scope workspace:writeMake OfferThe offer LETTER is a deterministic draft from the owner's own data - no model writes a decision. Delivery is the owner's (the mail rail is gated); the screen says so.
  • POST /hiring/applications/{application_id}/reject · scope workspace:writeReject Application
  • GET /hiring/applications/{application_id}/resume · scope workspace:readRead Resume
  • GET /hiring/internal-openings · scope workspace:readInternal OpeningsST7: published internal-first posts, visible to the signed-in team only - the promotion path a cashier can actually see.
  • GET /hiring/posts · scope workspace:readList Posts
  • POST /hiring/posts · scope workspace:writeCreate Post
  • PATCH /hiring/posts/{post_id} · scope workspace:writeUpdate Post
  • POST /hiring/posts/{post_id}/interview-questions · scope workspace:writeDraft Interview QuestionsQuestion DRAFTS from the post's own requirements, for the human interviewer - stored on the post so the whole team asks from one sheet.
  • GET /hris/employees · scope workspace:readList Employees
  • POST /hris/employees · scope workspace:writeCreate Employee
  • GET /hris/employees/{employee_id} · scope workspace:readGet Employee
  • PATCH /hris/employees/{employee_id} · scope workspace:writeUpdate Employee
  • DELETE /hris/employees/{employee_id} · scope workspace:writeDelete Employee
  • GET /hris/employees/{employee_id}/direct-reports · scope workspace:readList Direct Reports
  • POST /hris/employees/{employee_id}/invite · scope workspace:writeInvite Employee
  • POST /hris/employees/{employee_id}/on-leave · scope workspace:writeMark On Leave
  • POST /hris/employees/{employee_id}/rehire · scope workspace:writeRehire EmployeeEH1 - THE WAY BACK. The SAME employee row returns, so reviews, warnings, documents and transitions stay one unbroken record; the old ending folds into the transition history instead of being erased. Sign-in access does NOT return by itself - it died with the termination, and a fresh invite is the honest way in.
  • POST /hris/employees/{employee_id}/reinstate · scope workspace:writeReinstate Employee
  • POST /hris/employees/{employee_id}/terminate · scope workspace:writeTerminate Employee
  • GET /hris/locations · scope workspace:readList Locations
  • POST /hris/locations · scope workspace:writeCreate Location
  • PATCH /hris/locations/{location_id} · scope workspace:writeUpdate Location
  • GET /hris/my-organizations · scope workspace:readMy Organizations
  • GET /hris/public/staff-invite/{code} · scope workspace:readRead Staff InviteThe join page's data - org and employee NAME only, never the roster. No auth: the code is unguessable and this reveals exactly what the invitee already knows.
  • POST /hris/staff-invite/{code}/claim · scope workspace:writeClaim Staff InviteSigned in (fresh from signup or not), the code becomes a key: the login links to the employee record and org membership is minted. System cursor - the claimant is by definition not yet a member, so RLS correctly cannot see the rows.
  • POST /id-cards/employees/{employee_id}/card · scope workspace:writeMake CardCompose (or re-compose) the card. Reuses the employee's standing employment verification or mints one - the QR is that verification's public link, so ONE revocation story covers the card and the reference letter alike.
  • GET /id-cards/employees/{employee_id}/card.png · scope workspace:readRead Card
  • POST /id-cards/employees/{employee_id}/photo · scope workspace:writeSet Photo
  • GET /network/directory · scope workspace:readDirectoryEvery org with a LIVE storefront that has not stepped out of the directory. Served through the public-read context because every fact here is already on a public page - being signed in is required only because the directory is a tenant-space tool.
  • POST /network/partnerships · scope workspace:writePropose Partnership
  • GET /network/partnerships · scope workspace:readList PartnershipsBoth directions, with the counterpart named and the referral evidence counted from the click ledger - a real count of recorded rows, never an estimate.
  • POST /network/partnerships/{partnership_id}/connect-supplier · scope workspace:writeConnect SupplierTurns an ACCEPTED supplier partnership into a supplier row the buying org syncs from. New items always land as DRAFTS here (auto_publish=false) - the tenant's existing supplier dial flips that, behind the same publish gate every go-live passes through.
  • POST /network/partnerships/{partnership_id}/decide · scope workspace:writeDecide PartnershipTHE SECOND HAND. Only the RECEIVING org's owner decides - the proposer holding both pens would make the two-hand a fiction.
  • POST /network/partnerships/{partnership_id}/end · scope workspace:writeEnd PartnershipEither side's owner may end an accepted partnership. History stays - the row becomes 'ended', it does not vanish, and a fresh proposal is possible afterwards.
  • POST /network/partnerships/{partnership_id}/storefront · scope workspace:writeToggle StorefrontEach side dials its OWN storefront only - the partner's page is the partner's design.
  • PUT /network/profile/{organization_id} · scope workspace:writeSet Profile
  • POST /staff-time/clock · scope workspace:writeClockThe employee's own login toggles their own clock - in if out, out if in.
  • POST /staff-time/employees/{employee_id}/kiosk-pin · scope workspace:writeSet Kiosk PinThe OWNER or a manager sets it and hands it over - never self-set, never readable back (a hash is stored). A duplicate PIN in the same org is refused: the PIN is the identity at the kiosk.
  • POST /staff-time/kiosk-clock · scope workspace:writeKiosk ClockThe store tablet (signed in as any team member) clocks whoever scans their card or, with no card to hand, enters their PIN. The card or the PIN identifies the EMPLOYEE; the signed-in session only proves the device belongs to the org - a stranger's tablet cannot fish for PINs or replay cards. ONE door, ONE writer: both paths resolve an employee and hand it to `_toggle_clock`, the same arithmetic th…
  • GET /staff-time/pto · scope workspace:readList Pto
  • POST /staff-time/pto · scope workspace:writeRequest Pto
  • POST /staff-time/pto/{request_id}/decide · scope workspace:writeDecide Pto
  • GET /staff-time/shifts · scope workspace:readList Shifts
  • POST /staff-time/shifts · scope workspace:writeCreate Shift
  • DELETE /staff-time/shifts/{shift_id} · scope workspace:writeDelete Shift
  • GET /staff-time/timesheet · scope workspace:readTimesheet
  • GET /storefront-blog/audience · scope workspace:readAudience
  • GET /storefront-blog/issues · scope workspace:readList Issues
  • POST /storefront-blog/issues/from-post/{post_id} · scope workspace:writeIssue From PostDrafts an issue from a published post: the post's title as subject, its HTML as the body, plus the mandatory unsubscribe footer every real newsletter carries.
  • POST /storefront-blog/issues/{issue_id}/simulate-send · scope workspace:writeSimulate SendThe gated 'send': records the real active-subscriber count and marks the issue sent_simulated. NO EMAIL LEAVES THE PLATFORM - real dispatch waits for the standing real-communications sign-off, and will slot in behind this same issue model.
  • GET /storefront-blog/posts · scope workspace:readList Posts
  • POST /storefront-blog/posts · scope workspace:writeCreate Post
  • PATCH /storefront-blog/posts/{post_id} · scope workspace:writeUpdate Post
  • DELETE /storefront-blog/posts/{post_id} · scope workspace:writeDelete Post
  • POST /storefront-blog/posts/{post_id}/publish · scope workspace:writeToggle Publish
  • POST /storefront-campaigns/compose · scope workspace:writeCompose CampaignAI-drafts a campaign from a plain brief - subject + simple HTML paragraphs - and saves it as an editable DRAFT issue. The composer never sends anything.
  • PATCH /storefront-campaigns/issues/{issue_id} · scope workspace:writeEdit Issue
  • GET /storefront-campaigns/subscribers · scope workspace:readList Subscribers
  • POST /storefront-campaigns/subscribers/bulk-add · scope workspace:writeBulk AddAdds emails the tenant already has a relationship with. Honest accounting: invalid addresses and existing subscribers are counted, never silently swallowed.
  • POST /storefront-campaigns/subscribers/{subscriber_id}/remove · scope workspace:writeMember RemoveMember-initiated removal marks unsubscribed rather than deleting - the audit-friendly shape, and the person never silently reappears via a later bulk-add.
  • POST /storefront-campaigns/subscribers/{subscriber_id}/tags · scope workspace:writeSet Tags
  • GET /storefront-links/links · scope workspace:readList Links
  • POST /storefront-links/links · scope workspace:writeCreate Link
  • PATCH /storefront-links/links/{link_id} · scope workspace:writeUpdate Link
  • DELETE /storefront-links/links/{link_id} · scope workspace:writeDelete Link
  • GET /storefront-links/short · scope workspace:readList Short Links
  • POST /storefront-links/short · scope workspace:writeCreate Short Link
  • PATCH /storefront-links/short/{code} · scope workspace:writeToggle Short Link
  • GET /tenant-data/export/{organization_id}/contacts.csv · scope workspace:readExport Contacts
  • GET /tenant-data/export/{organization_id}/manifest · scope workspace:readExport ManifestWhat the org owns, counted from the real tables. Always available - no meeting, no retention call, no waiting period. The counts name what the CSVs carry out.
  • GET /tenant-data/export/{organization_id}/products.csv · scope workspace:readExport Products
  • POST /tenant-data/import · scope workspace:writeStart Import
  • POST /tenant-data/import/{job_id}/apply · scope workspace:writeApply ImportTHE SECOND HAND. Writes through the writers that already exist, drafts always, and returns an honest tally - a row that could not land is reported by line, not dropped.
  • POST /tenant-data/import/{job_id}/discard · scope workspace:writeDiscard Import
  • PATCH /tenant-data/import/{job_id}/mapping · scope workspace:writeEdit MappingThe human hand on the mapping. Every column it touches is re-labelled 'hand' - an AI guess survives only where the owner left it standing.
  • GET /tenant-data/imports · scope workspace:readList Imports
  • GET /theme · scope workspace:readGet Theme
  • GET /theme/domains · scope workspace:readList Domains
  • POST /theme/domains/claim · scope workspace:writeClaim DomainStart (or restart) ownership verification for a domain. Re-claiming an existing PENDING row keeps its token, so a tenant who already published the TXT record and comes back later does not have to edit their DNS again. A VERIFIED row is returned untouched - re-issuing a token there would spuriously un-verify a working domain.
  • POST /theme/domains/verify · scope workspace:writeVerify DomainPerform the REAL DNS lookup and record the outcome.
  • DELETE /theme/domains/{domain} · scope workspace:writeRemove DomainWithdraw a claim. Routing stops immediately, because both the routing lookup and the certificate gate require a verified row to exist.
  • PATCH /theme/draft · scope workspace:writeUpdate Theme Draft
  • GET /theme/live · scope workspace:readGet Live ThemeDashboard-only auth for now. The response contract is shaped so a future genuinely-public variant only swaps the auth dependency (JWT -> domain-resolved org) -- the body never changes. Returns empty defaults rather than 404 when nothing has been published yet, so a future public renderer degrades gracefully.
  • POST /theme/logo · scope workspace:writeUpload Theme Logo
  • GET /theme/logo · scope workspace:readGet Theme Logo
  • GET /theme/my-organizations · scope workspace:readMy Organizations
  • GET /theme/preview · scope workspace:readPreview Theme`page` accepted and currently unused (no public pages exist yet) -- always returns the draft snapshot. Swapping in real page-specific rendering later changes only the response body, not the URL contract callers already use.
  • POST /theme/publish · scope workspace:writePublish Theme
  • GET /theme/site-analytics · scope workspace:readSite AnalyticsLast-30-days storefront traffic for the org's own members. Aggregated daily counters only -- there is no per-visitor data to show, by design. Human and crawler traffic are reported SEPARATELY (migration 262). `unclassified` is traffic recorded before bot separation existed; it is a genuine mixture, so it is neither hidden nor folded into the human figure, and it ages out of the 30-day window by i…
  • GET /theme/site-funnel · scope workspace:readSite FunnelLast-30-days conversion funnel for this org's storefront. Three real stages, from data that already exists (no new table): page views (human only) -> intent started -> intent claimed HONEST ABOUT WHAT STAGE 1 IS. site_visit_daily counts PAGE VIEWS, not unique visitors - there is deliberately no per-visitor record to count. One person browsing four pages is four views. So the view->inten…
  • GET /theme/site-preview · scope workspace:readSite PreviewThe Theme Editor's real-storefront preview: same payload shape as the public GET /public/site/{slug}, but themed with the org's DRAFT instead of its live version. Authenticated + membership-checked -- drafts never leak to anonymous visitors; the public content blocks are collected through the same public-read cursor the real storefront uses, so preview can't show anything the live site wouldn't.
  • GET /theme/site-preview-detail · scope workspace:readSite Preview DetailPer-PAGE draft preview (STOREFRONT_GAPS "Per-PAGE preview targets"): the storefront's course / service / event / product detail pages, rendered by the editor's iframe with the DRAFT theme even before the site is published. Same membership gate as /site-preview; the detail rows come from the very loaders the public endpoints use, through the same public-read cursor - so a preview can never reveal a…
  • GET /theme/versions · scope workspace:readList Theme Versions
  • POST /theme/versions/duplicate · scope workspace:writeDuplicate Theme VersionSaves a named, unpublished copy of an existing version into the library -- does not touch the current draft or the live pointer (rollback covers restores).
  • POST /theme/versions/rollback · scope workspace:writeRollback Theme VersionCopy-forward rollback: creates a NEW version row that becomes live, and resets the draft to match, so the editor never shows a stale draft silently diverging from the just-restored live state. History is never rewritten.
  • GET /translate/audio · scope workspace:readList Jobs
  • POST /translate/audio · scope workspace:writeUpload Audio
  • GET /translate/audio/{job_id}/download · scope workspace:readDownload
  • POST /translate/audio/{job_id}/dub · scope workspace:writeCreate Dub
  • POST /translate/audio/{job_id}/dub-consent · scope workspace:writeFile Dub Consent
  • GET /translate/documents · scope workspace:readList Jobs
  • POST /translate/documents · scope workspace:writeCreate Job
  • GET /translate/documents/{job_id}/download · scope workspace:readDownload Result
  • GET /translate/images · scope workspace:readList Jobs
  • POST /translate/images · scope workspace:writeUpload Image
  • GET /translate/images/{job_id}/download · scope workspace:readDownload
  • GET /wallet-passes/issuances/{issuance_id} · scope workspace:readGet Issuance
  • GET /wallet-passes/issuances/{issuance_id}/pass-payload · scope workspace:readGet Pass PayloadReal, correctly-structured pass content, honestly labeled unsigned unless a real credential is genuinely configured (checked live, never assumed).
  • POST /wallet-passes/issuances/{issuance_id}/revoke · scope workspace:writeRevoke Issuance
  • GET /wallet-passes/my-organizations · scope workspace:readMy Organizations
  • GET /wallet-passes/signing-status · scope workspace:readSigning StatusReal, live-checked readiness — never a cached/assumed value.
  • GET /wallet-passes/templates · scope workspace:readList Templates
  • POST /wallet-passes/templates · scope workspace:writeCreate Template
  • GET /wallet-passes/templates/{template_id} · scope workspace:readGet Template
  • PATCH /wallet-passes/templates/{template_id} · scope workspace:writeUpdate Template
  • DELETE /wallet-passes/templates/{template_id} · scope workspace:writeDelete Template
  • GET /wallet-passes/templates/{template_id}/issuances · scope workspace:readList Issuances
  • POST /wallet-passes/templates/{template_id}/issuances · scope workspace:writeCreate Issuance
  • POST /watch · scope workspace:writeCreate Watch
  • GET /watch · scope workspace:readWatch BoardThe board. Opening it takes at most ONE lawful look per watch per interval; inside the interval the last snapshot is re-read instead of the rival's server. Changes are the diff of the two latest OK snapshots, with the readings as evidence.
  • POST /watch/field-intel · scope workspace:writeAdd Field Intel
  • PATCH /watch/field-intel/{intel_id} · scope workspace:writeCorrect Field IntelThe tenant's hand corrects the AI's reading - and the origin says so.
  • PATCH /watch/{watch_id} · scope workspace:writeUpdate Watch
  • POST /workspace-documents/approvals/{approval_id}:decide · scope workspace:writeDecide Approval
  • PATCH /workspace-documents/collaborators/{collaborator_id} · scope workspace:writePatch Collaborator
  • DELETE /workspace-documents/collaborators/{collaborator_id} · scope workspace:writeRevoke Collaborator
  • POST /workspace-documents/collaborators/{collaborator_id}:respond · scope workspace:writeRespond Invite
  • DELETE /workspace-documents/comments/{comment_id} · scope workspace:writeDelete Comment
  • POST /workspace-documents/comments/{comment_id}:reopen · scope workspace:writeReopen Comment
  • POST /workspace-documents/comments/{comment_id}:resolve · scope workspace:writeResolve Comment
  • POST /workspace-documents/projects/{project_id}/approvals · scope workspace:writeRequest Approval
  • POST /workspace-documents/projects/{project_id}/collaborators · scope workspace:writeAdd Collaborator
  • GET /workspace-documents/projects/{project_id}/comments · scope workspace:readList Comments
  • POST /workspace-documents/projects/{project_id}/comments · scope workspace:writeAdd Comment
  • POST /workspace-documents/projects/{project_id}/share-links · scope workspace:writeCreate Share Link
  • GET /workspace-documents/projects/{project_id}/sharing · scope workspace:readGet Sharing
  • PATCH /workspace-documents/projects/{project_id}/sharing · scope workspace:writePatch Sharing
  • POST /workspace-documents/projects/{project_id}:restore · scope workspace:writeRestore Project
  • POST /workspace-documents/projects/{project_id}:takedown · scope workspace:writeTakedown Project
  • POST /workspace-documents/reports · scope workspace:writeFile Report
  • GET /workspace-documents/reports · scope workspace:readList Reports
  • PATCH /workspace-documents/reports/{report_id} · scope workspace:writePatch Report
  • DELETE /workspace-documents/share-links/{link_id} · scope workspace:writeRevoke Share Link
  • GET /workspace-documents/shared-with-me · scope workspace:readShared With Me
  • GET /workspace/audit · scope workspace:readList Audit EventsRelies entirely on audit_events' own RLS policy (migration 009) to scope results to orgs the caller is a member of, or everything for the platform owner role - no additional filtering needed here.
  • GET /workspace/automations · scope workspace:readList Automations
  • POST /workspace/automations · scope workspace:writeCreate Automation
  • GET /workspace/automations/catalog · scope workspace:readCatalogWhat can be built. Named on the screen so a tenant is choosing from what exists rather than imagining what might.
  • DELETE /workspace/automations/{automation_id} · scope workspace:writeDelete Automation
  • PATCH /workspace/automations/{automation_id}/active · scope workspace:writeSet Active
  • POST /workspace/automations/{automation_id}/test · scope workspace:writeTest AutomationWould this rule have matched? Evaluates the conditions and performs NOTHING. A rule people cannot try before trusting is a rule they will not trust - and the only safe way to offer that is a path that cannot create anything at all.
  • GET /workspace/booking/bookings · scope workspace:readList BookingsThe status filter belongs HERE, not on the screen. The screen showed only confirmed bookings and filtered the list after fetching it. That is harmless while the list is whole and wrong the moment it is a page: the pager would count every booking including cancelled ones and announce "showing 1-25 of 40" above a list of six. A pager that miscounts is worse than no pager - it is a number people tru…
  • POST /workspace/booking/bookings/{booking_id}/cancel · scope workspace:writeCancel Booking
  • GET /workspace/booking/types · scope workspace:readList Types
  • POST /workspace/booking/types · scope workspace:writeCreate Type
  • PATCH /workspace/booking/types/{type_id} · scope workspace:writeUpdate Type
  • DELETE /workspace/booking/types/{type_id} · scope workspace:writeDelete Type
  • PUT /workspace/booking/types/{type_id}/availability · scope workspace:writeSet Availability
  • GET /workspace/brand-kits · scope workspace:readList Brand KitsReal Brand Kit Manager (Workspace tools manifest id 79) - a personal registry of brand guidelines (palette, voice, logo) other AI-drafting tools across the platform can eventually reference, closing a real, previously-nonexistent gap. Never exposes logo_storage_path (a raw filesystem path), matching AuthorProfileOut's own established precedent.
  • POST /workspace/brand-kits · scope workspace:writeCreate Brand Kit
  • PATCH /workspace/brand-kits/{kit_id} · scope workspace:writeUpdate Brand Kit
  • DELETE /workspace/brand-kits/{kit_id} · scope workspace:writeDelete Brand Kit
  • POST /workspace/brand-kits/{kit_id}/logo · scope workspace:writeUpload Brand Kit LogoReal logo upload, same enhance_image() + local-disk pattern already proven for Publishers' house logo (publishers_foundation.py).
  • GET /workspace/brand-kits/{kit_id}/logo · scope workspace:readGet Brand Kit Logo
  • GET /workspace/calendar/calendars · scope workspace:readList Calendars
  • POST /workspace/calendar/calendars · scope workspace:writeCreate Calendar
  • POST /workspace/calendar/check-conflicts · scope workspace:writeCheck ConflictsWhat would this new event collide with? Advisory - it never refuses the booking. Double-booking is sometimes deliberate, and a calendar that forbids it is one people work around by not using it.
  • GET /workspace/calendar/events · scope workspace:readList Events
  • POST /workspace/calendar/events · scope workspace:writeCreate Event
  • PATCH /workspace/calendar/events/{event_id} · scope workspace:writeUpdate Event
  • DELETE /workspace/calendar/events/{event_id} · scope workspace:writeDelete Event
  • POST /workspace/calendar/events/{event_id}/respond · scope workspace:writeRespondYour answer, and only yours. The policy allows nobody else to write this row.
  • GET /workspace/calendar/free-busy · scope workspace:readFree BusyWhen am I actually busy? Declined invitations and cancelled events are not busy. A conflict check that counts them tells people they are unavailable when they are free, and the first time somebody is told they are busy during an hour they know is empty, they stop believing the calendar.
  • GET /workspace/compliance-audit · scope workspace:readList Compliance Audit EventsSame real audit_events table and the same RLS-only access control as list_audit_events above - the compliance lens is purely a WHERE filter on `action`'s real dotted-domain prefix, no new schema or migration.
  • GET /workspace/contacts · scope workspace:readList ContactsReal personal contacts registry (Workspace tools manifest id 26, FitinContacts) - same personal-by-default scoping as Notes/Task Registry, mirrored field-for-field from workspace_note.
  • POST /workspace/contacts · scope workspace:writeCreate Contact
  • PATCH /workspace/contacts/{contact_id} · scope workspace:writeUpdate Contact
  • DELETE /workspace/contacts/{contact_id} · scope workspace:writeDelete Contact
  • GET /workspace/docker-status · scope workspace:readDocker StatusReal host-level Docker introspection (Workspace tools manifest id 112) - shells out to the real `docker ps` CLI (a fixed, hardcoded command with no interpolated input, matching tools.py's own nvidia-smi subprocess pattern), never fabricated. Local-dev-only infrastructure - this reports on whatever's genuinely running on THIS machine, not a remote fleet.
  • GET /workspace/files · scope workspace:readList Files
  • POST /workspace/files · scope workspace:writeUpload File
  • POST /workspace/files/ai-draft · scope workspace:writeCreate Ai Drafted Document
  • POST /workspace/files/blank · scope workspace:writeCreate Blank Document
  • DELETE /workspace/files/{file_id} · scope workspace:writeDelete File
  • POST /workspace/files/{file_id}/analyze-spreadsheet · scope workspace:writeAnalyze Spreadsheet
  • GET /workspace/files/{file_id}/download · scope workspace:readDownload File
  • POST /workspace/files/{file_id}/office-callback · scope workspace:writeOffice CallbackCalled directly by the OnlyOffice Document Server, not by a Fitinty session. Must respond {"error": 0} regardless of outcome, per OnlyOffice's own callback contract.
  • GET /workspace/files/{file_id}/office-config · scope workspace:readGet Office Config
  • GET /workspace/files/{file_id}/office-content · scope workspace:readGet Office ContentFetched directly by the OnlyOffice Document Server container - no Fitinty session available, so access is gated by a short-lived HMAC-signed token instead.
  • GET /workspace/forms · scope workspace:readList Forms
  • POST /workspace/forms · scope workspace:writeCreate Form
  • POST /workspace/forms/purge-expired · scope workspace:writePurge ExpiredEnforces the retention promise. Retention that is only ever displayed is not retention. This is callable by the form owner and is safe to run repeatedly; RLS confines it to that owner's own forms, so it can never reach across tenants even if it is called by the wrong person.
  • GET /workspace/forms/templates/catalog · scope workspace:readTemplate CatalogThe starter shelf - key, title, why, and the question count, never the full definitions (the builder shows those after the mint, where they are editable).
  • POST /workspace/forms/templates/{key} · scope workspace:writeCreate From TemplateMint a DRAFT form from a template. An unknown key is refused - the catalog is the picker, never free text (the silent-zero lesson).
  • GET /workspace/forms/{form_id} · scope workspace:readGet Form
  • PATCH /workspace/forms/{form_id} · scope workspace:writeUpdate Form
  • DELETE /workspace/forms/{form_id} · scope workspace:writeDelete Form
  • POST /workspace/forms/{form_id}/analyze · scope workspace:writeAnalyze Responses
  • GET /workspace/forms/{form_id}/export.csv · scope workspace:readExport Csv
  • PUT /workspace/forms/{form_id}/fields · scope workspace:writeReplace FieldsThe builder saves the whole question list at once. Ids are preserved where the client sends one, because a field id is what an already-submitted answer is filed under. Regenerating them on every save would orphan every response the form has already collected.
  • GET /workspace/forms/{form_id}/responses · scope workspace:readList Responses
  • DELETE /workspace/forms/{form_id}/responses/{response_id} · scope workspace:writeDelete Response
  • GET /workspace/forms/{form_id}/summary · scope workspace:readForm Summary
  • GET /workspace/health · scope workspace:readFull HealthEverything, with the addresses and the topology. Owner only — this describes the Forge.
  • GET /workspace/health/service/{key} · scope workspace:readService HealthDeliberately tenant-readable, and deliberately narrow. A tenant is entitled to know why the thing they just clicked is not working — that is the entire point of this wave. What they are NOT given is the address, the host, or anything else about Fitinty's infrastructure: `status`, `label` and `breaks` are the whole payload a screen needs, and the screens ask for exactly the one service they are ab…
  • GET /workspace/home-trends · scope workspace:readHome TrendsThe tenant home's org-level series (AN3) - storefront visitors, subscribers, link clicks, event registrations - from the series spine, beside the pulse rather than inside it so the heartbeat stays fast. A read failure is a sentence, not a missing section.
  • GET /workspace/local-ports · scope workspace:readLocal PortsReal host-level listening-port introspection (Workspace tools manifest id 117) - reuses the exact `psutil` dependency Tools' own hardware telemetry already proved works on this machine, in the same lazy-import-with-graceful-fallback style. Local-dev-only infrastructure, matching Docker Status (id 112) - reports on whatever's genuinely listening on THIS machine right now, not a remote fleet. Proces…
  • GET /workspace/meetings · scope workspace:readList Meetings
  • POST /workspace/meetings · scope workspace:writeCreate Meeting
  • GET /workspace/meetings/capacity · scope workspace:readCapacityWhat standing up Fitinty-hosted rooms would actually require. Owner only - these are infrastructure decisions, not tenant settings.
  • GET /workspace/meetings/hosted-status · scope workspace:readRead Hosted StatusReadable by any tenant: somebody who cannot create a Fitinty room deserves to know why, and that it is a platform state rather than something wrong with their account.
  • PATCH /workspace/meetings/{meeting_id} · scope workspace:writeUpdate Meeting
  • DELETE /workspace/meetings/{meeting_id} · scope workspace:writeDelete Meeting
  • GET /workspace/my-tenants · scope workspace:readList My TenantsReal cross-pillar aggregator (Workspace tools manifest id 55, "Tenant Dashboard") - queries each pillar's own real tenant/membership tables directly (see _TENANT_QUERIES) and merges the results. No new schema, no fabricated data - a genuinely empty pillar section here means the caller really has no tenant there.
  • GET /workspace/notes · scope workspace:readList Notes
  • POST /workspace/notes · scope workspace:writeCreate Note
  • PATCH /workspace/notes/{note_id} · scope workspace:writeUpdate Note
  • DELETE /workspace/notes/{note_id} · scope workspace:writeDelete Note
  • GET /workspace/payments · scope workspace:readList PaymentsFitintyPay Control Center (manifest item 89) - a real, owner-only, read-only view of real Stripe TEST-mode transactions, sourced live from Medusa's own admin API (the same integration Marketplace checkout already uses). Deliberately read-only - no payout/refund/capture action exists here. Real money movement stays hard-blocked regardless of the 2026-08-01 "ignore all locks" instruction - that inst…
  • POST /workspace/perfection-reviews · scope workspace:writeCreate Review
  • GET /workspace/perfection-reviews · scope workspace:readList Reviews
  • GET /workspace/perfection-reviews/{review_id} · scope workspace:readGet Review
  • POST /workspace/perfection-reviews/{review_id}/advance · scope workspace:writeAdvance ReviewRun the current Council stage's review (draft/audit/verify) and advance.
  • POST /workspace/perfection-reviews/{review_id}/reject · scope workspace:writeReject Review
  • POST /workspace/perfection-reviews/{review_id}/seal · scope workspace:writeSeal ReviewThe Emperor applies the Imperial Seal - final approval after peer review.
  • GET /workspace/pillar-health · scope workspace:readPillar HealthReal, platform-wide Pillar Health Board (Workspace tools manifest id 126) - distinct from both the Pillar Registry Viewer (build status, no live data) and /my-tenants (personal, per-user). Owner-bypass RLS gives a genuine, unfiltered COUNT(*) per pillar's own real tenant table - the same f"SELECT count(*)..." pattern already proven in kpi.py's own TENANT_TABLES, reused here as a health/activity si…
  • GET /workspace/pulse · scope workspace:readTenant Pulse
  • GET /workspace/risk-alerts · scope workspace:readList Risk AlertsReal Risk Alerts (Workspace tools manifest id 10) - distinct from the already-built, static High-Risk Task Viewer (id 52). Joins the AI Agency's own real high-risk build_task ledger against this platform's own per-user acknowledgment table, so a genuinely NEW risk (never acknowledged by this owner session) is visibly distinguishable from one already reviewed - the real proactive-alerting mechanism…
  • POST /workspace/risk-alerts/{task_id}/acknowledge · scope workspace:writeAcknowledge Risk Alert
  • GET /workspace/search · scope workspace:readSearch
  • GET /workspace/search/sources · scope workspace:readList SourcesWhat search covers. Named on the screen so a tenant knows what a result set does and does not include, rather than inferring it from what happened to come back.
  • POST /workspace/songs · scope workspace:writeCreate Song
  • GET /workspace/songs · scope workspace:readList Songs
  • DELETE /workspace/songs/{song_id} · scope workspace:writeDelete Song
  • GET /workspace/songs/{song_id}/audio · scope workspace:readDownload Song Audio
  • GET /workspace/system-health · scope workspace:readSystem HealthReal reachability checks only - no fabricated "Council"/agent status. There's no governed agent registry yet (see Part 02's agent control plane gap in CLAUDE.md), so reporting on AI "council members" here would just be invented data. This checks the services that actually exist: Postgres, Redis, and Medusa.
  • GET /workspace/tasks · scope workspace:readList Tasks
  • POST /workspace/tasks · scope workspace:writeCreate Task
  • GET /workspace/tasks/all · scope workspace:readList All TasksMaster Task Registry Viewer (manifest item 45) - a genuinely distinct view from the personal Task Registry: every task across every user, owner-only. RLS still scopes the underlying SELECT (a non-owner caller would just see their own tasks), the 403 here is the intended access boundary for this specific cross-user view.
  • PATCH /workspace/tasks/{task_id} · scope workspace:writeUpdate Task
  • DELETE /workspace/tasks/{task_id} · scope workspace:writeDelete Task
  • GET /workspace/tenant-brand-voice · scope workspace:readTenant Brand VoiceReal, personal, cross-pillar aggregator (Workspace tools manifest id 65, "Tenant Brand Voice Settings") - the caller's own real voice-profile COUNT(*) across every games/publishers tenant they belong to, no new schema. A tenant genuinely having zero profiles renders as an honest zero, not hidden - the actual add/edit UI stays on each pillar's own real page (href), never duplicated here.
  • GET /workspace/tenant-region-settings · scope workspace:readTenant Region SettingsReal, personal, cross-pillar aggregator (Workspace tools manifest id 66, "Tenant Language / Region Settings") - the caller's own real region/languages/time_zone fields across every farms/manufacturing/ logistics tenant they belong to, no new schema. Editing happens through the pillar's own already-existing PATCH endpoint (patch_href), never a duplicate write path - this endpoint is read-only.
  • POST /workspace/tickets · scope workspace:writeCreate Ticket
  • GET /workspace/tickets/all · scope workspace:readList All TicketsOwner-only support inbox - RLS still applies, so a non-owner caller would just get back their own tickets rather than everyone's, but the endpoint itself is intended for owner use.
  • GET /workspace/tickets/mine · scope workspace:readList My Tickets
  • PATCH /workspace/tickets/{ticket_id} · scope workspace:writeUpdate Ticket Status
  • GET /workspace/tickets/{ticket_id}/replies · scope workspace:readList Replies
  • POST /workspace/tickets/{ticket_id}/replies · scope workspace:writeCreate Reply
Services - 57 doors - services:read, services:write

Bookable work: listings, requests and the calendar behind them.

  • GET /services/availability · scope services:readList Rules
  • POST /services/availability · scope services:writeAdd Rule
  • DELETE /services/availability/{rule_id} · scope services:writeDelete Rule
  • POST /services/booking-series/{series_id}/stop · scope services:writeStop Series
  • POST /services/bookings · scope services:writeCreate Booking
  • GET /services/bookings/mine · scope services:readList My Bookings
  • GET /services/bookings/providing · scope services:readList Bookings To Fulfill
  • PATCH /services/bookings/{booking_id} · scope services:writeUpdate Booking Status
  • GET /services/bookings/{booking_id}/deliverables · scope services:readList Deliverables
  • POST /services/bookings/{booking_id}/deliverables · scope services:writeUpload Deliverable
  • POST /services/bookings/{booking_id}/make-recurring · scope services:writeMake RecurringSL3 - the customer turns one booking into a standing order. Completion of each visit mints the NEXT pending booking (event-driven - no daemon; nothing a tenant waits on runs on a timer). Either side can stop the series any time.
  • POST /services/bookings/{booking_id}/pay · scope services:writeStart Booking CheckoutThe customer's pay door on their own booking. Stripe hosts the page; the customer pays the PROVIDER; Fitinty's cut rides Stripe's own split. One purchase per booking - a cancelled checkout leaves a pending row this door REUSES rather than doubles.
  • GET /services/bookings/{booking_id}/payment · scope services:readBooking Payment StatusThree honest answers, kept apart: paid (with the captured moment), pending (a checkout was started and no money moved), or no payment record at all.
  • POST /services/bookings/{booking_id}/satisfaction · scope services:writeSubmit Satisfaction
  • GET /services/bookings/{booking_id}/satisfaction · scope services:readGet Satisfaction
  • GET /services/bookings/{booking_id}/value-report · scope services:readGet Value Report
  • POST /services/bookings/{booking_id}/value-report · scope services:writeSave Value Report
  • POST /services/clients · scope services:writeCreate Client
  • GET /services/clients/mine · scope services:readList My Clients
  • PATCH /services/clients/{client_id} · scope services:writeUpdate Client
  • DELETE /services/clients/{client_id} · scope services:writeDelete Client
  • GET /services/corrections/mine · scope services:readList My Corrections
  • PATCH /services/corrections/{correction_id} · scope services:writeResolve Correction
  • GET /services/deliverables/{deliverable_id}/download · scope services:readDownload Deliverable
  • GET /services/leads · scope services:readList LeadsThe desk: every lead with its DERIVED band and reasons, plus the conversion truth - how many leads became captured money. The numbers competitors cannot have, beside the promise they cannot make.
  • POST /services/leads · scope services:writeAdd Manual LeadThe tenant's own hand - a phone call, a walk-in, a referral heard at church.
  • POST /services/leads/from-form-response · scope services:writeFile Form Response As LeadThe tenant's door from Fitinty Forms: an answer that reads like an enquiry gets FILED to the desk by a human hand - deliberate, never auto-classified. The email is found deterministically (the response's answer to an email-kind question).
  • GET /services/leads/offers · scope services:readList Offers
  • POST /services/leads/offers/{offer_id}/withdraw · scope services:writeWithdraw Offer
  • PATCH /services/leads/{lead_id} · scope services:writeSet Lead Status
  • GET /services/listing-kits/mine · scope services:readMy Listing KitThe kit for the provider's own vertical - or an honest 'none for yours yet'.
  • POST /services/listing-kits/mine/mint · scope services:writeMint Listing KitMints the vertical's kit as INACTIVE drafts - building is free, and switching each one live walks through pay-to-open like any hand-built listing. Skips any title the provider already has, so minting twice doubles nothing.
  • GET /services/listings · scope services:readList Active Listings
  • POST /services/listings · scope services:writeCreate Listing
  • GET /services/listings/mine · scope services:readList My Listings
  • PATCH /services/listings/{listing_id} · scope services:writeUpdate Listing
  • POST /services/listings/{listing_id}/feature · scope services:writeFeature ListingSL3 - featured placement in the public market, Fitinty's own future stream. Until Dodo takes live payments this is the EMPEROR'S COMP HAND (the promoted- listings pattern): owner-only, days counted, and the market shows the label - a featured listing is marked, never smuggled.
  • POST /services/listings/{listing_id}/image · scope services:writeSet Listing ImageA service listing's FACE (LST cross-pillar pass, 2026-08-30): the pillar had no images at all - migration 436 added the column and this gives it a writer. Enhanced through the same pipeline as every platform image; stored on the API's own disk (deliverables' pattern) and served publicly through the storefront's own gated route.
  • GET /services/listings/{listing_id}/packages · scope services:readList Packages
  • POST /services/listings/{listing_id}/packages · scope services:writeCreate Package
  • GET /services/packages/mine · scope services:readList My Packages
  • PATCH /services/packages/{package_id} · scope services:writeUpdate Package
  • POST /services/providers · scope services:writeCreate Provider
  • GET /services/providers/mine · scope services:readGet My Provider Endpoint
  • PATCH /services/providers/{provider_id} · scope services:writeUpdate Provider
  • GET /services/providers/{slug}/listings · scope services:readGet Provider Listings
  • POST /services/quotes · scope services:writeCreate QuoteThe provider's estimate, born a DRAFT. It must answer something real - a request or a lead of this organization - so a quote can never be conjured at a stranger.
  • GET /services/quotes · scope services:readList Quotes
  • POST /services/quotes/accept/{token} · scope services:writeAccept QuoteSIGNED-IN acceptance (the claim posture). The job is born on the standing booking object with NO TIME YET - trades schedule after acceptance, and 'not scheduled yet' is stated, never invented.
  • POST /services/quotes/{quote_id}/send · scope services:writeSend QuoteMarks the quote SENT and hands back the accept link. Sending the link stays the provider's hand - chat, a reply, on paper; mail drafts wait for the rail.
  • POST /services/quotes/{quote_id}/withdraw · scope services:writeWithdraw Quote
  • POST /services/requests · scope services:writeCreate Request
  • GET /services/requests/incoming · scope services:readList Incoming Requests
  • GET /services/requests/mine · scope services:readList My Requests
  • PATCH /services/requests/{request_id} · scope services:writeUpdate Request Status
  • GET /services/verticals · scope services:readList VerticalsReal, curated vertical + field-template catalog - Vertical Business Operations v1 (Connected Apps cost-elimination audit, 2026-08-12). The frontend already mirrors this in lib/vertical-fields.ts for zero-latency rendering; this endpoint exists so any other real client (a future mobile app, a script) has one authoritative source, and so the backend's own validation logic below is provably reading t…
  • POST /services/webhook · scope services:writeServices Stripe WebhookThe pillar's own verified door, education's shape exactly: signature or refusal, captured events only, idempotent by the pending->paid guard + the fee ledger's unique payment id.
Ministries - 46 doors - ministries:read, ministries:write

Congregations: members, giving, live events and the people who run them.

  • PUT /give-payments/page · scope ministries:writeUpsert PageCreate or update this organization's giving page. Defaults to INACTIVE. A giving link that goes live the moment a row exists is a link that goes live before anybody has read the wording on it - the same rule booking pages already follow.
  • GET /ministries/archive/search · scope ministries:readSearch Archive
  • GET /ministries/churches · scope ministries:readList Churches
  • POST /ministries/churches · scope ministries:writeCreate Church
  • GET /ministries/churches/mine · scope ministries:readGet My Church Endpoint
  • GET /ministries/churches/{church_id}/live-events · scope ministries:readList Events
  • GET /ministries/churches/{church_id}/members · scope ministries:readList Church Members
  • POST /ministries/churches/{church_id}/members · scope ministries:writeAdd Church Member
  • GET /ministries/cohorts · scope ministries:readList Cohorts
  • POST /ministries/cohorts · scope ministries:writeCreate Cohort
  • GET /ministries/congregation/care-requests · scope ministries:readList Care RequestsA congregation's pastors, leaders and admins see every request that came to THEIR congregation; anyone else sees only what the requester let the congregation see. The leaders_only wall is enforced HERE by real role, not by the screen.
  • PATCH /ministries/congregation/care-requests/{request_id} · scope ministries:writeSet Care StatusTending a request is pastoral work: only those who tend care for the congregation it came to may move it, and a refusal returns nothing of the request - not its words, not its asker.
  • POST /ministries/congregation/funds/{fund_id}/share · scope ministries:writeMake Fund ShareableMints the fund's public campaign slug - the link travels beyond the congregation; every gift through it still lands on the church's own account at 0%, designated to this fund.
  • GET /ministries/congregation/groups · scope ministries:readList GroupsEvery group the person can see, or one congregation's when the desk names it - a person in two congregations must not see one church's choir on the other's desk.
  • POST /ministries/congregation/groups · scope ministries:writeCreate Group
  • GET /ministries/congregation/groups/{group_id}/members · scope ministries:readList Group MembersWho is in the group, leaders first - the roster a leader serves.
  • POST /ministries/congregation/groups/{group_id}/members · scope ministries:writeAdd Group MemberAdding someone already in the group changes their role - the one door for both.
  • DELETE /ministries/congregation/groups/{group_id}/members/{membership_id} · scope ministries:writeRemove Group MemberLeaving a group removes the person from THAT group only - they stay in the congregation.
  • GET /ministries/congregation/mine · scope ministries:readMy Congregations
  • GET /ministries/congregation/newcomers · scope ministries:readList NewcomersThe shepherd's desk: everyone who reached toward the church, with the honest measure beside it - who returned, who connected, who became a member. Exclusive to this congregation; never resold, never pooled.
  • POST /ministries/congregation/newcomers · scope ministries:writeAdd Newcomer
  • PATCH /ministries/congregation/newcomers/{newcomer_id} · scope ministries:writeUpdate Newcomer
  • GET /ministries/events · scope ministries:readList Events
  • POST /ministries/events · scope ministries:writeCreate Event
  • GET /ministries/guardrails · scope ministries:readList Guardrails
  • POST /ministries/guardrails · scope ministries:writePropose GuardrailAI may propose content but cannot publish doctrinal material automatically (spec section 7.3) - every guardrail lands as 'draft' regardless of who creates it; a separate approve call moves it forward.
  • POST /ministries/guardrails/{guardrail_id}/approve · scope ministries:writeApprove Guardrail
  • GET /ministries/integrations · scope ministries:readList Integrations
  • POST /ministries/live-events · scope ministries:writeSchedule Event
  • GET /ministries/live-events/{event_id} · scope ministries:readGet Event
  • GET /ministries/live-events/{event_id}/attendance · scope ministries:readEvent AttendanceAttendance audit metrics (church staff). The 'engagement heatmap' is the per-attendee join_count + presence window.
  • POST /ministries/live-events/{event_id}/end · scope ministries:writeEnd Event
  • POST /ministries/live-events/{event_id}/join · scope ministries:writeJoin EventRecord attendance and return the room. Any church member may join.
  • POST /ministries/live-events/{event_id}/leave · scope ministries:writeLeave Event
  • POST /ministries/live-events/{event_id}/start · scope ministries:writeStart Event
  • GET /ministries/pastor-workspace · scope ministries:readPastor Workspace
  • GET /ministries/sermons · scope ministries:readList Sermons
  • POST /ministries/sermons · scope ministries:writeCreate Sermon
  • GET /ministries/sermons/{sermon_id} · scope ministries:readGet Sermon
  • GET /ministries/sermons/{sermon_id}/download · scope ministries:readDownload Sermon
  • POST /ministries/sermons/{sermon_id}/multiplier · scope ministries:writeSermon MultiplierAI-assisted "sermon multiplier" (spec section 9) - drafts a summary, discussion questions, and a Bible-study outline from the sermon's own transcript only, never invented content. Labeled draft, never auto-published (section 9: "No automatic public publishing"; section 7.4: "Fitinty shall not claim theological authority").
  • PATCH /ministries/sermons/{sermon_id}/status · scope ministries:writeSet Sermon Status
  • PATCH /ministries/sermons/{sermon_id}/transcript · scope ministries:writeSet TranscriptReal transcription (Faster-Whisper per spec section 22) is a bigger infrastructure decision not made yet - this is the manual-entry shell the spec's own build sequence (N08) calls "Transcription shell" before a real engine is wired in, matching this project's honest-scoping pattern for AI/media infra it hasn't built yet (e.g. Publishers' audiobook narration).
  • GET /ministries/studies · scope ministries:readList Studies
  • POST /ministries/studies · scope ministries:writeCreate Study
  • PATCH /ministries/studies/{lesson_id}/status · scope ministries:writeSet Study Status
Media - 553 doors - media:read, media:write

Productions and published media, and the rights that travel with them.

  • GET /design-canvas/assets · scope media:readList Assets
  • POST /design-canvas/assets/generate · scope media:writeGenerate AssetReal FLUX.1-dev generation on the local GPU via ComfyUI - reuses the shared flux_image_engine. Slow by nature (a real diffusion render); the frontend calls this with a long timeout.
  • POST /design-canvas/assets/upload · scope media:writeUpload Asset
  • GET /design-canvas/assets/{asset_id}/file · scope media:readAsset File
  • GET /design-canvas/designs · scope media:readList Designs
  • POST /design-canvas/designs · scope media:writeCreate Design
  • GET /design-canvas/designs/{design_id} · scope media:readGet Design
  • PATCH /design-canvas/designs/{design_id} · scope media:writeSave Design
  • DELETE /design-canvas/designs/{design_id} · scope media:writeDelete Design
  • POST /design-canvas/designs/{design_id}/duplicate · scope media:writeDuplicate Design
  • GET /design-canvas/templates · scope media:readList TemplatesThe shelf - key, name and size only; the layout ships when one is minted.
  • POST /design-canvas/templates/{key} · scope media:writeMint From TemplateMints an ordinary DRAFT design from the shelf - from here on it is any other design: the editor owns it, nothing auto-publishes anywhere.
  • GET /identity/organizations/{org}/pack · scope media:readGet Pack
  • PUT /identity/organizations/{org}/pack · scope media:writePut Pack
  • POST /identity/song/{project_id}:apply · scope media:writeApply To Song
  • POST /identity/song/{project_id}:check · scope media:writeCheck Song
  • POST /identity/video/{project_id}:apply · scope media:writeApply To VideoA new brief version (source 'identity_pack') with the shot grammar and voice merged in. Nothing renders; existing shots are not rewritten - the check names them instead.
  • POST /identity/video/{project_id}:check · scope media:writeCheck Video
  • POST /media-payments/licenses · scope media:writeCreate LicenseThe tenant sells usage rights to an episode's audio: the TERMS live in words on the row, the pay link travels like a sponsorship, and the file is delivered only on captured money. The offer IS the pending purchase - one table, one rail.
  • GET /media-payments/licenses · scope media:readList Licenses
  • GET /media-payments/purchases/mine · scope media:readMy MediaTHE LISTENER'S OWN PURCHASES (2026-09-11, the customer Space's "Elsewhere"): premium episodes, show memberships, licences, sponsorships and tips a person paid for, across every show. A purchase names its person by `buyer_user_id` when they were signed in, and otherwise by the email they paid with - the standing ownership rule (own_identity), decided here per row on the system cursor. PAID only: a …
  • POST /media-payments/sponsorships · scope media:writeCreate SponsorshipThe offer IS the pending purchase row - one table, no parallel invoice system. The tenant sends the returned link; nothing is emailed for them (no mail rail).
  • GET /media-payments/sponsorships · scope media:readList SponsorshipsThe tenant's sponsorship desk: every offer with its state and its link.
  • POST /media-payments/sponsorships/{purchase_id}/void · scope media:writeVoid SponsorshipWithdrawing an offer nobody paid. A PAID sponsorship is history, never voidable - money that moved stays on the books.
  • POST /media-payments/webhook · scope media:writeMedia Stripe WebhookThe pillar's own verified door, education's shape exactly: signature or refusal, captured events only.
  • GET /media/assets · scope media:readList Assets
  • POST /media/assets · scope media:writeUpload Asset
  • GET /media/assets/{asset_id}/download · scope media:readDownload Asset
  • GET /media/audience-insights · scope media:readList Audience Insights
  • POST /media/audience-insights/generate · scope media:writeGenerate Audience InsightExplicitly synthetic (spec section 16: 'Safe Core supports synthetic audience segments') - real external audience data collection is locked regardless of instruction, same as every other real-external-data boundary in this pillar.
  • GET /media/briefs · scope media:readList Briefs
  • POST /media/briefs · scope media:writeCreate Brief
  • POST /media/briefs/{brief_id}/approve · scope media:writeApprove Brief
  • GET /media/calendar · scope media:readList Calendar
  • POST /media/calendar · scope media:writeCreate Calendar Item
  • GET /media/campaigns · scope media:readList Campaigns
  • POST /media/campaigns · scope media:writeCreate Campaign
  • PATCH /media/campaigns/{campaign_id}/status · scope media:writeTransition Campaign
  • GET /media/distribution · scope media:readList Distribution Jobs
  • POST /media/distribution · scope media:writeCreate Distribution Job
  • PATCH /media/distribution/{job_id}/status · scope media:writeTransition Distribution JobReal external posting states are never enabled here (spec section 15) - `simulated` is the terminal success state, there is no `posted`/`published` status in the allow-list at all.
  • GET /media/experiments · scope media:readList Experiments
  • POST /media/experiments · scope media:writeCreate Experiment
  • PATCH /media/experiments/variants/{variant_id}/results · scope media:writeRecord Variant Results
  • PATCH /media/experiments/variants/{variant_id}/short-link · scope media:writeBind Variant Short LinkMD6 - binds the variant to ONE of the tenant's own short links. From then on its reading is the link's real clicks; the synthetic numbers stop mattering for this variant and the banner comes off honestly.
  • POST /media/experiments/{experiment_id}/complete · scope media:writeComplete ExperimentDeclares a winner by real conversion-rate comparison across recorded synthetic variants - refuses to declare a winner from insufficient data (spec section 12: 'must avoid declaring winners from insufficient data'), using a minimum-sample-size floor rather than just picking whichever variant has the highest raw rate.
  • GET /media/hooks · scope media:readList Hooks
  • POST /media/hooks · scope media:writeCreate Hook
  • GET /media/integrations · scope media:readList Integrations
  • GET /media/live-recaps · scope media:readList Recaps
  • POST /media/live-recaps · scope media:writeCreate Recap
  • POST /media/live-recaps/{recap_id}/draft-summary · scope media:writeDraft Recap Summary
  • GET /media/provenance · scope media:readList Provenance
  • POST /media/provenance · scope media:writeCreate ProvenanceSigning status stays 'unsigned' by default - production C2PA signing is a separately-gated real infrastructure decision (spec section 26/37), not something recorded here just because a row was created.
  • GET /media/reputation · scope media:readList Reputation Signals
  • POST /media/reputation/generate · scope media:writeGenerate Reputation Signal
  • POST /media/workspaces · scope media:writeCreate Workspace
  • GET /media/workspaces/mine · scope media:readGet My Workspace Endpoint
  • DELETE /multimedia-factory/items/{iid} · scope media:writeUnlink ItemUnlinks; the project itself stays in its factory.
  • GET /multimedia-factory/productions · scope media:readList Productions
  • POST /multimedia-factory/productions · scope media:writeCreate Production
  • GET /multimedia-factory/productions/{pid} · scope media:readGet Production
  • DELETE /multimedia-factory/productions/{pid} · scope media:writeDelete ProductionRemoves the production and its links. The song / video / deck projects stay in their factories - they are the person's work, not the production's.
  • GET /multimedia-factory/productions/{pid}/brief.md · scope media:readProduction Brief MdThe one-page production brief a human collaborator (a musician, an editor, a designer) can work from - the same spine the factories got.
  • POST /multimedia-factory/productions/{pid}/certificate · scope media:writeIssue Certificate
  • GET /multimedia-factory/productions/{pid}/certificate · scope media:readGet Certificate
  • PUT /multimedia-factory/productions/{pid}/intake · scope media:writePut Intake
  • POST /multimedia-factory/productions/{pid}/items · scope media:writeLink ItemLink an existing project (made before the production, or by hand) so it is rolled up too.
  • PUT /multimedia-factory/productions/{pid}/message · scope media:writePut Message
  • POST /multimedia-factory/productions/{pid}/plan · scope media:writePlan ProductionThe Producer: message spine + which outputs / roles / order / why + facts + open questions. Falls back to the deterministic rules (and says so) when the model is busy or unavailable.
  • PUT /multimedia-factory/productions/{pid}/plan · scope media:writePatch PlanThe person's edits: outputs on/off, roles, titles, briefs, order - kept as 'user' authored.
  • POST /multimedia-factory/productions/{pid}/produce · scope media:writeProduceCreates the chosen outputs as real projects in their factories, briefs prefilled from the spine, sources copied into the deck's ledger, facts handed to the song and the video. Kinds already linked are skipped (no duplicates); nothing renders here.
  • POST /multimedia-factory/productions/{pid}/sources · scope media:writeAdd Source
  • DELETE /multimedia-factory/sources/{sid} · scope media:writeDelete Source
  • PATCH /podcasts/episodes/{episode_id} · scope media:writeUpdate Episode
  • DELETE /podcasts/episodes/{episode_id} · scope media:writeDelete Episode
  • POST /podcasts/episodes/{episode_id}/audio · scope media:writeUpload Episode Audio
  • POST /podcasts/episodes/{episode_id}/publish · scope media:writeToggle Episode Publish
  • GET /podcasts/shows · scope media:readList Shows
  • POST /podcasts/shows · scope media:writeCreate Show
  • PATCH /podcasts/shows/{show_id} · scope media:writeUpdate Show
  • POST /podcasts/shows/{show_id}/artwork · scope media:writeUpload Artwork
  • GET /podcasts/shows/{show_id}/episodes · scope media:readList Episodes
  • POST /podcasts/shows/{show_id}/episodes · scope media:writeCreate Episode
  • POST /podcasts/shows/{show_id}/publish · scope media:writeToggle Show Publish
  • GET /presentation-factory/accessibility-reports/{rid} · scope media:readGet Report
  • GET /presentation-factory/accessibility-reports/{rid}/markdown · scope media:readReport MarkdownThe handable version. Plain Markdown, because the person who asked for this may well need to paste it into an e-mail or a tender response.
  • POST /presentation-factory/approvals/{approval_id}:decide · scope media:writeDecide Approval
  • POST /presentation-factory/certificates/{cid}:verify · scope media:writeVerify Certificate
  • PATCH /presentation-factory/claims/{cid} · scope media:writePatch Claim
  • DELETE /presentation-factory/claims/{cid} · scope media:writeDelete Claim
  • PATCH /presentation-factory/collaborators/{collaborator_id} · scope media:writePatch Collaborator
  • DELETE /presentation-factory/collaborators/{collaborator_id} · scope media:writeRevoke Collaborator
  • POST /presentation-factory/collaborators/{collaborator_id}:respond · scope media:writeRespond Invite
  • DELETE /presentation-factory/comments/{comment_id} · scope media:writeDelete Comment
  • POST /presentation-factory/comments/{comment_id}:reopen · scope media:writeReopen Comment
  • POST /presentation-factory/comments/{comment_id}:resolve · scope media:writeResolve Comment
  • GET /presentation-factory/datasets/{did} · scope media:readGet Dataset
  • DELETE /presentation-factory/datasets/{did} · scope media:writeDelete Dataset
  • PUT /presentation-factory/datasets/{did}/columns · scope media:writePatch Dataset ColumnsEdit types/units (the honest metadata) - names and data are the snapshot's, immutable.
  • POST /presentation-factory/datasets/{did}:refresh · scope media:writeRefresh DatasetA NEW snapshot from the current file / a fresh paste, with the drift named. Charts keep showing their reviewed snapshot until someone updates them - never silently.
  • DELETE /presentation-factory/domains/{did} · scope media:writeDelete Domain
  • POST /presentation-factory/domains/{did}:verify · scope media:writeVerify DomainLook for the token at the .well-known path. Nothing about this is taken on trust: the fetch goes through the same guard as any other, and a near-miss is reported as a failure.
  • GET /presentation-factory/exports/{eid}/certificate · scope media:readGet Certificate
  • GET /presentation-factory/exports/{eid}/file · scope media:readExport File
  • POST /presentation-factory/followups/{fid}:answer · scope media:writeAnswer FollowupSaves the presenter's answer. Evidence ids are checked against this deck's ledger - an id from somewhere else is dropped, not trusted - and an answer with none is stored `unsourced`.
  • POST /presentation-factory/followups/{fid}:decline · scope media:writeDecline FollowupNot every question can be answered, and pretending otherwise is worse than saying so. A declined follow-up keeps the question and the reason; it just stops being open work.
  • POST /presentation-factory/followups/{fid}:draft · scope media:writeDraft AnswerDrafts, but never saves - the presenter is the one who answers the room. The draft comes back with its evidence and with any invented number named out loud.
  • POST /presentation-factory/followups/{fid}:publish · scope media:writePublish Followup
  • POST /presentation-factory/layout:preview · scope media:writeLayout Preview
  • DELETE /presentation-factory/locks/{lid} · scope media:writeUnlock Route
  • GET /presentation-factory/media/available · scope media:readAvailable MediaWhat this person can attach: their ready Song Factory renders and Video Factory exports, each with the certificate it already carries.
  • PATCH /presentation-factory/media/{mid} · scope media:writePatch Media
  • DELETE /presentation-factory/media/{mid} · scope media:writeDelete Media
  • GET /presentation-factory/media/{mid}/captions · scope media:readMedia Captions
  • GET /presentation-factory/media/{mid}/file · scope media:readMedia File
  • GET /presentation-factory/narrations/{nid} · scope media:readGet Narration
  • DELETE /presentation-factory/narrations/{nid} · scope media:writeDelete Narration
  • GET /presentation-factory/narrations/{nid}/audio · scope media:readNarration Audio
  • GET /presentation-factory/narrations/{nid}/captions · scope media:readNarration Captions
  • POST /presentation-factory/narrations/{nid}/share · scope media:writeShare Talk
  • POST /presentation-factory/narrations/{nid}/share:revoke · scope media:writeRevoke Talk
  • GET /presentation-factory/narrations/{nid}/transcript · scope media:readNarration TranscriptThe whole talk as text - the thing a person can read instead of listening, or search.
  • GET /presentation-factory/narrations/{nid}/what-would-be-published · scope media:readWhat Would Be PublishedShown BEFORE the share button does anything: the exact words that would become public, in order, so the decision gets made looking at the thing rather than at a checkbox.
  • POST /presentation-factory/organizations/{org_id}/claims · scope media:writeCreate Claim
  • GET /presentation-factory/organizations/{org_id}/claims · scope media:readList Claims
  • PATCH /presentation-factory/organizations/{org_id}/claims/{cid} · scope media:writeUpdate ClaimEditing the library NEVER touches a deck. The response says how many decks now differ, so the consequence of the edit is visible at the moment it is made rather than discovered later.
  • GET /presentation-factory/organizations/{org_id}/claims/{cid}/usage · scope media:readClaim Usage
  • POST /presentation-factory/organizations/{org_id}/claims/{cid}:retire · scope media:writeRetire ClaimRetiring names the decks it affects. You cannot retire evidence into the void.
  • POST /presentation-factory/organizations/{org_id}/claims/{cid}:verify · scope media:writeVerify ClaimA person went and looked. That is the only thing that resets the clock - there is no automatic re-verification here, because nothing about a stored row can tell you the world still agrees.
  • GET /presentation-factory/organizations/{org_id}/claims:drift · scope media:readDriftEverywhere the library and a deck no longer agree, plus the claims nobody has checked. This is the report that answers "which eleven decks still say 1,240?".
  • POST /presentation-factory/organizations/{org_id}/domains · scope media:writeAdd DomainRegisters a domain as the organisation's own. It starts DECLARED - which means somebody typed it. Verification is a separate, provable step.
  • GET /presentation-factory/organizations/{org_id}/domains · scope media:readList Domains
  • GET /presentation-factory/organizations/{org_id}/insights · scope media:readOrg InsightsThe same honesty, one level up: across an organisation's decks. RLS decides which decks are visible - this adds no visibility of its own.
  • PUT /presentation-factory/organizations/{organization_id}/brand-profile · scope media:writePut Brand Profile
  • DELETE /presentation-factory/polls/{poll_id} · scope media:writeDelete Poll
  • GET /presentation-factory/projects · scope media:readList Projects
  • POST /presentation-factory/projects · scope media:writeCreate Project
  • GET /presentation-factory/projects/{pid} · scope media:readGet Project
  • DELETE /presentation-factory/projects/{pid} · scope media:writeDelete Project
  • GET /presentation-factory/projects/{pid}/accessibility · scope media:readCheck AccessibilityThe live view. Recomputed every time, so it is never stale - and never a certificate.
  • GET /presentation-factory/projects/{pid}/accessibility/reports · scope media:readList Reports
  • POST /presentation-factory/projects/{pid}/accessibility:issue · scope media:writeIssue ReportFreezes the report against this version, hashes it, and stores it. Failures do not block - a report that only exists when everything passes is a marketing document, and the whole value of this one is that it can say what is wrong. But issuing over failures is recorded.
  • GET /presentation-factory/projects/{pid}/brand · scope media:readGet Brand
  • POST /presentation-factory/projects/{pid}/brand:apply · scope media:writeApply BrandApply the organisation's Theme Editor tokens to the deck: colours, fonts, the logo (as a data URI at render time so exports stay self-contained), the required footer line.
  • POST /presentation-factory/projects/{pid}/brief · scope media:writeSave Brief
  • POST /presentation-factory/projects/{pid}/brief:strategist · scope media:writeStrategist
  • POST /presentation-factory/projects/{pid}/check:notes · scope media:writeCheck NotesDo the notes and the slide agree? Rules catch the numbers; the model catches the ones written in words. When the model is unavailable the rules still run, and the answer says which ran.
  • GET /presentation-factory/projects/{pid}/checks · scope media:readChecks
  • POST /presentation-factory/projects/{pid}/claims · scope media:writeAdd Claim
  • POST /presentation-factory/projects/{pid}/claims:adopt · scope media:writeAdopt ClaimsCopies library claims into this deck. A retired claim is refused rather than quietly adopted.
  • POST /presentation-factory/projects/{pid}/datasets · scope media:writeCreate Dataset
  • POST /presentation-factory/projects/{pid}/deck · scope media:writeSave Deck
  • POST /presentation-factory/projects/{pid}/deck:architect · scope media:writeArchitect
  • GET /presentation-factory/projects/{pid}/delivery · scope media:readDelivery ReportAcross every ended session of this deck: what the plan said, what actually happened, and the slides that keep costing you time. Nothing here is modelled - it is your own stopwatch.
  • GET /presentation-factory/projects/{pid}/diff · scope media:readDiffCompare two versions. `base` defaults to the previous version, `against` to the current one.
  • POST /presentation-factory/projects/{pid}/evidence:map · scope media:writeEvidence Map
  • POST /presentation-factory/projects/{pid}/exports · scope media:writeExport Deck
  • POST /presentation-factory/projects/{pid}/followups · scope media:writeAdd FollowupA question asked out loud that never made it to a phone still deserves an answer.
  • GET /presentation-factory/projects/{pid}/followups · scope media:readList Followups
  • PATCH /presentation-factory/projects/{pid}/freshness · scope media:writeSet HorizonHow long this deck's evidence stays fresh. A safety briefing and a conference keynote do not age at the same rate, and the platform should not pretend to know which one this is.
  • POST /presentation-factory/projects/{pid}/import:pptx · scope media:writeImport Pptx DeckA .pptx comes in as a DRAFT deck version - text and speaker notes - with a report of what did not come across. Nothing is overwritten; it is a new version to edit or discard.
  • GET /presentation-factory/projects/{pid}/insights · scope media:readDeck InsightsWhat this deck's rooms are telling you. Read-only; nothing here is stored.
  • POST /presentation-factory/projects/{pid}/locks · scope media:writeLock Route
  • GET /presentation-factory/projects/{pid}/logo · scope media:readProject Logo
  • POST /presentation-factory/projects/{pid}/media · scope media:writeAttach MediaAttach a Song Factory render or a Video Factory export, snapshotting its provenance and pulling its accessibility material (captions / lyrics) in.
  • GET /presentation-factory/projects/{pid}/media · scope media:readList Media
  • GET /presentation-factory/projects/{pid}/media/{sid} · scope media:readSource MediaA Song Factory render / Video export attached as a source, for the slide's <audio>/<video>.
  • POST /presentation-factory/projects/{pid}/narration · scope media:writeStart NarrationSpeaks the deck's own speaker notes. Refuses if there are no notes at all rather than inventing a talk - a narration built from headlines would be a script the presenter never wrote.
  • GET /presentation-factory/projects/{pid}/narrations · scope media:readList Narrations
  • GET /presentation-factory/projects/{pid}/parts · scope media:readParts Route
  • POST /presentation-factory/projects/{pid}/polls · scope media:writeCreate Poll
  • GET /presentation-factory/projects/{pid}/polls · scope media:readList Polls
  • GET /presentation-factory/projects/{pid}/preflight · scope media:readPreflightWhat to read in the two minutes before you start a session. Same assessment, ordered worst first and stripped to what a person can actually act on while walking to the front.
  • POST /presentation-factory/projects/{pid}/presence · scope media:writePresence Route
  • GET /presentation-factory/projects/{pid}/present · scope media:readPresentThe in-app accessible web deck for the CURRENT version (same renderer as the export).
  • GET /presentation-factory/projects/{pid}/readiness · scope media:readReadiness
  • POST /presentation-factory/projects/{pid}/rehearsal · scope media:writeStart RehearsalThe skeptical panel + the toughest questions, from the brief's own audience and objections. `use_rules` (or a busy GPU) gives the deterministic panel and says so.
  • POST /presentation-factory/projects/{pid}/sessions · scope media:writeStart SessionStarts a live session on the deck's CURRENT version and pins it. Unlike an export, this does NOT block on the quality checker: someone is standing in front of a room and the deck is what it is. The unresolved count comes back with the code so it is said out loud, not buried.
  • GET /presentation-factory/projects/{pid}/sessions · scope media:readList Sessions
  • GET /presentation-factory/projects/{pid}/since-approval · scope media:readSince ApprovalWhat has moved since somebody put their name to this. If nothing was ever approved it says so rather than quietly comparing against something else and calling it the same question.
  • POST /presentation-factory/projects/{pid}/slides/{part_id}:ai-edit · scope media:writeAi Edit Route
  • POST /presentation-factory/projects/{pid}/sources · scope media:writeAdd Source
  • POST /presentation-factory/projects/{pid}/sources:staleness · scope media:writeStalenessRun the evidence refresh now (same handler the automation loop runs): re-verify every claim's verbatim excerpt, mark vanished ones 'stale', report workspace-file drift.
  • POST /presentation-factory/projects/{pid}/sources:web · scope media:writeAdd Web SourceFetch a page and keep it as a snapshot. The permission field defaults to 'unknown' and stays the person's declaration - Fitinty does not decide that a page is reusable because it loaded.
  • POST /presentation-factory/projects/{pid}/suggestions · scope media:writeSuggest Route
  • POST /presentation-factory/projects/{pid}/templates/{tid}:apply · scope media:writeApply Template
  • POST /presentation-factory/projects/{pid}/theme · scope media:writeSet Theme
  • POST /presentation-factory/projects/{pid}/variants · scope media:writeCreate VariantA LOCALE VARIANT: a full linked deck in the target language - brief, sources and claims copied (evidence stays the single source of truth), slides machine-translated (marked, flagged 'native review needed' until a person marks it reviewed) and judged at the target language's text length. The parent deck is never touched.
  • POST /presentation-factory/projects/{pid}:mark-reviewed · scope media:writeMark ReviewedA person who read the variant in its language signs it off; recorded, not assumed.
  • POST /presentation-factory/projects/{project_id}/approvals · scope media:writeRequest Approval
  • POST /presentation-factory/projects/{project_id}/collaborators · scope media:writeAdd Collaborator
  • GET /presentation-factory/projects/{project_id}/comments · scope media:readList Comments
  • POST /presentation-factory/projects/{project_id}/comments · scope media:writeAdd Comment
  • POST /presentation-factory/projects/{project_id}/share-links · scope media:writeCreate Share Link
  • GET /presentation-factory/projects/{project_id}/sharing · scope media:readGet Sharing
  • PATCH /presentation-factory/projects/{project_id}/sharing · scope media:writePatch Sharing
  • POST /presentation-factory/projects/{project_id}:restore · scope media:writeRestore Project
  • POST /presentation-factory/projects/{project_id}:takedown · scope media:writeTakedown Project
  • GET /presentation-factory/rehearsals/{rid} · scope media:readGet Rehearsal
  • POST /presentation-factory/rehearsals/{rid}:answer · scope media:writeAnswer RehearsalGrade one typed answer against the claim ledger. An invented number is a failed answer.
  • POST /presentation-factory/rehearsals/{rid}:finish · scope media:writeFinish RehearsalThe readiness report: what held, what failed, which objections still have no home, timing.
  • POST /presentation-factory/rehearsals/{rid}:timing · scope media:writeTiming RehearsalThe timing drill: actual seconds per slide (measured by the client) vs the budgets. Pure.
  • POST /presentation-factory/reports · scope media:writeFile Report
  • GET /presentation-factory/reports · scope media:readList Reports
  • PATCH /presentation-factory/reports/{report_id} · scope media:writePatch Report
  • GET /presentation-factory/sessions/{sid} · scope media:readGet SessionThe presenter's own view: every question INCLUDING the hidden ones (with the reason recorded against them), live poll counts, and the running timeline.
  • DELETE /presentation-factory/sessions/{sid} · scope media:writeDelete Session
  • POST /presentation-factory/sessions/{sid}/followup-page:publish · scope media:writePublish PageMints (once) an unguessable link the presenter can hand back to the room. This publishes the answers they marked as publishable - nothing else, and nobody is e-mailed or texted.
  • POST /presentation-factory/sessions/{sid}/followup-page:revoke · scope media:writeRevoke Page
  • POST /presentation-factory/sessions/{sid}/followups:collect · scope media:writeCollect FollowupsTurns every question the room asked that was never answered into a follow-up item. Hidden questions are NOT collected - they were taken off the screen for a reason - but the count of them comes back, so the presenter is told what was left behind rather than left to assume.
  • POST /presentation-factory/sessions/{sid}/polls/{poll_id}:open · scope media:writeOpen PollOne poll open at a time - a room cannot answer two questions at once, and the DB index says so.
  • POST /presentation-factory/sessions/{sid}/polls:close · scope media:writeClose Poll
  • POST /presentation-factory/sessions/{sid}/questions/{qid}:answered · scope media:writeMark Answered
  • POST /presentation-factory/sessions/{sid}/questions/{qid}:hide · scope media:writeHide QuestionTakes a question off the room's screen. It is NOT deleted: the words stay, with who hid them and why, and the session summary counts them. Moderation you can audit is the only honest kind.
  • POST /presentation-factory/sessions/{sid}/questions/{qid}:restore · scope media:writeRestore Question
  • POST /presentation-factory/sessions/{sid}/runs/{run_id}:to-dataset · scope media:writePoll Run To DatasetPull a closed poll's numbers into the deck as a dataset - carrying the provenance sentence with them, permanently. The room's answer can be shown; it cannot be laundered into a statistic, because the sentence that says what it really is travels in the same row.
  • POST /presentation-factory/sessions/{sid}/slide · scope media:writeGo To SlideMoving on closes the previous slide's stopwatch. That is the whole source of the honest 'planned 4 minutes, took 11' report at the end - nothing is estimated after the fact.
  • POST /presentation-factory/sessions/{sid}:end · scope media:writeEnd SessionEnds the room and writes the record once. The part that matters most is the list of questions that were NEVER answered - the thing a presenter forgets by the time they reach the car park.
  • DELETE /presentation-factory/share-links/{link_id} · scope media:writeRevoke Share Link
  • GET /presentation-factory/shared-with-me · scope media:readShared With Me
  • PATCH /presentation-factory/sources/{sid} · scope media:writePatch Source
  • DELETE /presentation-factory/sources/{sid} · scope media:writeDelete Source
  • GET /presentation-factory/sources/{sid}/snapshots · scope media:readList Snapshots
  • GET /presentation-factory/sources/{sid}/text · scope media:readSource Text
  • POST /presentation-factory/sources/{sid}:recheck · scope media:writeRecheck Web SourceFetch the page again and say plainly whether it still says what was cited. Drift is reported, never silently applied: the old snapshot stays, and the claims keep pointing at what was read.
  • POST /presentation-factory/suggestions/{sid}:accept · scope media:writeAccept Route
  • POST /presentation-factory/suggestions/{sid}:reject · scope media:writeReject Route
  • GET /presentation-factory/templates · scope media:readList Templates
  • POST /presentation-factory/templates · scope media:writeSave Template
  • DELETE /presentation-factory/templates/{tid} · scope media:writeDelete Template
  • POST /presentation-factory/templates/{tid}:approve · scope media:writeApprove Template
  • GET /presentation-factory/versions/{vid} · scope media:readGet Version
  • POST /presentation-factory/versions/{vid}:restore · scope media:writeRestore Version
  • POST /song-factory/approvals/{approval_id}:decide · scope media:writeDecide Approval
  • GET /song-factory/bridges/cinema/projects · scope media:readList Cinema ProjectsCinema projects the caller can add a music track to (RLS-scoped) - the "Send to Cinema" picker.
  • GET /song-factory/bridges/cinema/renders · scope media:readList Scoreable RendersCompleted Cinema renders the caller can see (RLS on render_attempt / render_job / cinema_project does the scoping), newest first - the picker for "Score a Cinema render".
  • POST /song-factory/bridges/cinema/score-render · scope media:writeScore Cinema Render
  • POST /song-factory/bridges/cinema/send/{gen_id} · scope media:writeSend Generation To CinemaA ready/exported generation -> Cinema music asset + audio_track + provenance record, with the Rights Certificate issued first and summarised on the asset.
  • GET /song-factory/bridges/games/assets · scope media:readList Scoreable Game Assets
  • POST /song-factory/bridges/games/import · scope media:writeImport Game Asset
  • GET /song-factory/bridges/games/projects · scope media:readList Game Projects
  • POST /song-factory/bridges/games/send/{gen_id} · scope media:writeSend Generation To Game-> game_asset audio, source fitinty_song_factory, certificate summary in `license`, file_hash = audio sha, review_state proposed (the game's own review gate still decides).
  • POST /song-factory/bridges/import · scope media:writeImport Source
  • GET /song-factory/bridges/marketing/assets · scope media:readList Scoreable Marketing Assets
  • GET /song-factory/bridges/marketing/campaigns · scope media:readList Marketing CampaignsSend targets: a profile (music for the brand) or a specific campaign under it.
  • POST /song-factory/bridges/marketing/import · scope media:writeImport Marketing Asset
  • POST /song-factory/bridges/marketing/send/{gen_id} · scope media:writeSend Generation To Marketing-> marketing_content_asset with audio (migration 272) + marketing_provenance_record + marketing_cross_pillar_link (destination song_factory, accepted).
  • GET /song-factory/bridges/media/assets · scope media:readList Scoreable Media AssetsImages and videos in Media workspaces the caller can see (is_media_staff via RLS).
  • POST /song-factory/bridges/media/import · scope media:writeImport Media AssetA Media image -> Visual Score reference; a Media video -> measured video cue. Same rows, same declarations as a direct upload, with the provenance of where it came from.
  • POST /song-factory/bridges/media/send/{gen_id} · scope media:writeSend Generation To MediaA ready/exported generation -> Media audio asset (ai_generated, consent_confirmed, the certificate summarised in rights_metadata) + media_provenance_record.
  • GET /song-factory/bridges/media/workspaces · scope media:readList Media Workspaces
  • GET /song-factory/bridges/publishers/covers · scope media:readList Scoreable Covers
  • POST /song-factory/bridges/publishers/import · scope media:writeImport Cover
  • POST /song-factory/bridges/send/{gen_id} · scope media:writeSend Generation
  • GET /song-factory/bridges/sources · scope media:readList SourcesOne list for the UI: [{pillar, kind: image|video, ref_id, label}] across pillars, each RLS-scoped by its own query. Empty pillars simply contribute nothing.
  • GET /song-factory/bridges/targets · scope media:readList TargetsEverywhere a finished render can be sent: [{pillar, ref_id, label}]. Workspace is always available (it is the person's own files).
  • GET /song-factory/bridges/workspace/files · scope media:readList Scoreable Workspace Files
  • POST /song-factory/bridges/workspace/import · scope media:writeImport Workspace File
  • POST /song-factory/bridges/workspace/send/{gen_id} · scope media:writeSend Generation To Workspace-> two workspace_file rows: the FLAC and "<name>.certificate.json" beside it. workspace_file has no metadata column; a delivered song never travels without its certificate, so the certificate becomes a file of its own.
  • GET /song-factory/certificates/{cert_id}/verify · scope media:readVerify Certificate
  • PATCH /song-factory/clips/{clip_id} · scope media:writePatch Clip
  • DELETE /song-factory/clips/{clip_id} · scope media:writeDelete Clip
  • POST /song-factory/clips/{clip_id}:split · scope media:writeSplit ClipNon-destructive split: the clip ends at `at`, a new clip starts there with the matching source offset. Fades stay where they were (in on the left part, out on the right).
  • PATCH /song-factory/collaborators/{collaborator_id} · scope media:writePatch Collaborator
  • DELETE /song-factory/collaborators/{collaborator_id} · scope media:writeRevoke Collaborator
  • POST /song-factory/collaborators/{collaborator_id}:respond · scope media:writeRespond Invite
  • DELETE /song-factory/comments/{comment_id} · scope media:writeDelete Comment
  • POST /song-factory/comments/{comment_id}:reopen · scope media:writeReopen Comment
  • POST /song-factory/comments/{comment_id}:resolve · scope media:writeResolve Comment
  • GET /song-factory/generations/{gen_id} · scope media:readGet Generation
  • GET /song-factory/generations/{gen_id}/audio · scope media:readGeneration Audio
  • GET /song-factory/generations/{gen_id}/certificate · scope media:readGet Certificate
  • GET /song-factory/generations/{gen_id}/consistency · scope media:readGeneration ConsistencyScore a generation against its project's identity using what was ACTUALLY sent.
  • GET /song-factory/generations/{gen_id}/export · scope media:readExport GenerationDownload + certificate in one act (plan 12.1 - the package is bigger later; today it is the master and the certificate). Marks the generation EXPORTED.
  • GET /song-factory/generations/{gen_id}/export-package · scope media:readExport PackagePlan 12.1 - the delivery package as one ZIP: the FLAC master, the WAV with the C2PA manifest (sandbox-signed when the SDK is present, else unsigned + the manifest JSON beside it), certificate.json, manifest.json, the .c2pa store when signed, and a README that says which is which and what is NOT warranted. Marks the generation exported.
  • GET /song-factory/generations/{gen_id}/provenance · scope media:readGet Provenance
  • GET /song-factory/generations/{gen_id}/provenance/asset · scope media:readProvenance Asset
  • POST /song-factory/generations/{gen_id}/provenance:issue · scope media:writeIssue Provenance
  • POST /song-factory/generations/{gen_id}/qc · scope media:writeQc GenerationThe audio quality gate (measured, with fixes): EBU R128 loudness, true peak, lead/trail silence, duration vs the brief. Report, never a block - the person decides.
  • POST /song-factory/generations/{gen_id}:cancel · scope media:writeCancel Generation
  • POST /song-factory/generations/{gen_id}:confirm · scope media:writeConfirm Generation
  • POST /song-factory/generations/{gen_id}:reroute · scope media:writeReroute GenerationAsk for a fallback estimate by hand - for a failed render that did not get one (no other model was loaded at the time), or to retry routing later. Same rules as the automatic offer.
  • DELETE /song-factory/locks/{lid} · scope media:writeUnlock Route
  • DELETE /song-factory/markers/{marker_id} · scope media:writeDelete Marker
  • GET /song-factory/models · scope media:readList Models
  • DELETE /song-factory/notes/{note_id} · scope media:writeDelete Note
  • GET /song-factory/projects · scope media:readList Projects
  • POST /song-factory/projects · scope media:writeCreate Project
  • POST /song-factory/projects/{pid}/locks · scope media:writeLock Route
  • GET /song-factory/projects/{pid}/parts · scope media:readParts Route
  • POST /song-factory/projects/{pid}/parts/{part_id}:ai-edit · scope media:writeAi Edit Route
  • POST /song-factory/projects/{pid}/presence · scope media:writePresence Route
  • POST /song-factory/projects/{pid}/suggestions · scope media:writeSuggest Route
  • GET /song-factory/projects/{project_id} · scope media:readGet Project
  • DELETE /song-factory/projects/{project_id} · scope media:writeDelete Project
  • POST /song-factory/projects/{project_id}/approvals · scope media:writeRequest Approval
  • POST /song-factory/projects/{project_id}/brief · scope media:writeSave Brief
  • POST /song-factory/projects/{project_id}/collaborators · scope media:writeAdd Collaborator
  • GET /song-factory/projects/{project_id}/comments · scope media:readList Comments
  • POST /song-factory/projects/{project_id}/comments · scope media:writeAdd Comment
  • GET /song-factory/projects/{project_id}/consistency · scope media:readProject ConsistencyScore the project's CURRENT brief against its bound identity (no render needed).
  • POST /song-factory/projects/{project_id}/generations:estimate · scope media:writeEstimate Generation
  • POST /song-factory/projects/{project_id}/lyrics · scope media:writeSave Lyrics
  • POST /song-factory/projects/{project_id}/lyrics:generate · scope media:writeGenerate Lyrics
  • POST /song-factory/projects/{project_id}/moment:compile · scope media:writeCompile Moment
  • POST /song-factory/projects/{project_id}/references · scope media:writeUpload ReferenceUpload a reference recording. Three declarations are required and stored verbatim with a timestamp; there is no identity detection and none is claimed (plan 7.5: the certificate's no-warranty line exists precisely because of what this cannot check).
  • GET /song-factory/projects/{project_id}/references · scope media:readList References
  • POST /song-factory/projects/{project_id}/share-links · scope media:writeCreate Share Link
  • GET /song-factory/projects/{project_id}/sharing · scope media:readGet Sharing
  • PATCH /song-factory/projects/{project_id}/sharing · scope media:writePatch Sharing
  • POST /song-factory/projects/{project_id}/sonic-identity:apply · scope media:writeApply IdentityOnly an APPROVED version is applied (brand/legal gate). Writes a brief version with the identity's rules enforced and binds the project to identity + version.
  • POST /song-factory/projects/{project_id}/sonic-identity:unbind · scope media:writeUnbind Identity
  • GET /song-factory/projects/{project_id}/timeline · scope media:readGet TimelineGet-or-create the project's timeline. A fresh timeline gets one empty track and tempo from the brief (bpm / time signature) when the brief has them.
  • POST /song-factory/projects/{project_id}/variations:estimate · scope media:writeEstimate VariationsPlan 5.2: one brief, N seeds, one estimate. Every row is a full generation (own certificate, own export); the group is how they are confirmed, rendered and shown together.
  • POST /song-factory/projects/{project_id}/videos · scope media:writeUpload Video
  • GET /song-factory/projects/{project_id}/videos · scope media:readList Videos
  • POST /song-factory/projects/{project_id}/visuals · scope media:writeUpload Visual
  • GET /song-factory/projects/{project_id}/visuals · scope media:readList Visuals
  • POST /song-factory/projects/{project_id}/visuals:analyze · scope media:writeAnalyze VisualsPer-image analysis (cached on the row) + a synthesized VisualMusicBrief. Returns both; applies nothing - the person edits and applies (plan 5.6 steps 5-7).
  • POST /song-factory/projects/{project_id}/visuals:apply · scope media:writeApply Visual Brief
  • POST /song-factory/projects/{project_id}:remix · scope media:writeRemix Project
  • POST /song-factory/projects/{project_id}:restore · scope media:writeRestore Project
  • POST /song-factory/projects/{project_id}:takedown · scope media:writeTakedown Project
  • GET /song-factory/provenance/sdk · scope media:readProvenance Sdk
  • DELETE /song-factory/references/{ref_id} · scope media:writeDelete Reference
  • GET /song-factory/references/{ref_id}/audio · scope media:readReference Audio
  • GET /song-factory/renders/{render_id}/audio · scope media:readRender Audio
  • POST /song-factory/reports · scope media:writeFile Report
  • GET /song-factory/reports · scope media:readList Reports
  • PATCH /song-factory/reports/{report_id} · scope media:writePatch Report
  • DELETE /song-factory/share-links/{link_id} · scope media:writeRevoke Share Link
  • GET /song-factory/shared-with-me · scope media:readShared With Me
  • GET /song-factory/sonic-identities · scope media:readList Identities
  • POST /song-factory/sonic-identities · scope media:writeCreate Identity
  • GET /song-factory/sonic-identities/organizations · scope media:readList Identity OrgsThe organizations this person can hold a Sonic Identity for (member or owner).
  • GET /song-factory/sonic-identities/{identity_id} · scope media:readGet Identity
  • GET /song-factory/sonic-identities/{identity_id}/series · scope media:readSeries ConsistencyEvery render across projects bound to this identity, scored - the 'episode consistency' view for a series or campaign (plan 5.7 / SF-031).
  • POST /song-factory/sonic-identities/{identity_id}/versions · scope media:writeAdd VersionEditing = a new version. Approval does not carry over: the new version is unapproved until an owner approves it (the identity's status drops back to draft if it was approved).
  • POST /song-factory/sonic-identities/{identity_id}:approve · scope media:writeApprove IdentityBrand / legal review (plan 5.7): an org OWNER approves the CURRENT version. Members can draft; only owners approve - the same line themes draw for publishing.
  • POST /song-factory/sonic-identities/{identity_id}:archive · scope media:writeArchive Identity
  • GET /song-factory/studio/peaks · scope media:readPeaks
  • POST /song-factory/suggestions/{sid}:accept · scope media:writeAccept Route
  • POST /song-factory/suggestions/{sid}:reject · scope media:writeReject Route
  • PATCH /song-factory/timelines/{timeline_id} · scope media:writePatch Timeline
  • POST /song-factory/timelines/{timeline_id}/clips · scope media:writeAdd Clip
  • POST /song-factory/timelines/{timeline_id}/clips:place · scope media:writePlace Clip
  • GET /song-factory/timelines/{timeline_id}/daw-package · scope media:readDaw PackagePlan 12.1 'aligned DAW export': mix.wav, stems/<track>.wav each rendered alone and padded to the full length (so they line up on import), markers.csv, edl.json, tempo.txt, lyrics.txt, certificates/<generation>.json for every render used, README.
  • POST /song-factory/timelines/{timeline_id}/markers · scope media:writeAdd Marker
  • POST /song-factory/timelines/{timeline_id}/notes · scope media:writeAdd Note
  • POST /song-factory/timelines/{timeline_id}/regenerate-region · scope media:writeRegenerate RegionA region -> a new render at the region's EXACT length. Writes a new brief version (the duration, and instrumental when no excerpt) and, with an excerpt, a new lyrics version - both append-only, so the regeneration is provenance, not a side channel. Returns the estimate; the person confirms it in Generate; when ready, `clips:place` drops it into the region.
  • POST /song-factory/timelines/{timeline_id}/renders · scope media:writeRender TimelineSynchronous delivery render of the mix (seconds for a few minutes of audio).
  • POST /song-factory/timelines/{timeline_id}/tracks · scope media:writeAdd Track
  • PATCH /song-factory/tracks/{track_id} · scope media:writePatch Track
  • DELETE /song-factory/tracks/{track_id} · scope media:writeDelete Track
  • POST /song-factory/variation-groups/{group_id}:cancel · scope media:writeCancel VariationsCancels every row of the grid that has not started rendering; ones already on the GPU finish (the runner skips canceled rows before it starts each one).
  • POST /song-factory/variation-groups/{group_id}:confirm · scope media:writeConfirm VariationsOne set of declarations for the whole grid; the same checks as a single confirm, applied to every row; one background runner for the group.
  • DELETE /song-factory/videos/{video_id} · scope media:writeDelete Video
  • GET /song-factory/videos/{video_id}/media · scope media:readVideo Media
  • POST /song-factory/videos/{video_id}:apply · scope media:writeApply Video BriefThe edited direction becomes a brief version with the clip's EXACT duration, the energy arc, the hit points, and video_reference_id for provenance.
  • POST /song-factory/videos/{video_id}:measure · scope media:writeMeasure VideoMeasure (cached), describe a few frames with the vision model, synthesize a direction. Returns measurement + direction; applies nothing.
  • DELETE /song-factory/visuals/{visual_id} · scope media:writeDelete Visual
  • GET /song-factory/visuals/{visual_id}/image · scope media:readVisual Image
  • GET /video-factory/animatics/{aid} · scope media:readGet Animatic
  • GET /video-factory/animatics/{aid}/file · scope media:readAnimatic File
  • POST /video-factory/approvals/{approval_id}:decide · scope media:writeDecide Approval
  • GET /video-factory/audio-masters/{mid} · scope media:readGet Master
  • GET /video-factory/audio-masters/{mid}/file · scope media:readMaster File
  • GET /video-factory/board-frames/{fid}/image · scope media:readBoard Image
  • POST /video-factory/certificates/{cert_id}:verify · scope media:writeVerify Certificate
  • PATCH /video-factory/collaborators/{collaborator_id} · scope media:writePatch Collaborator
  • DELETE /video-factory/collaborators/{collaborator_id} · scope media:writeRevoke Collaborator
  • POST /video-factory/collaborators/{collaborator_id}:respond · scope media:writeRespond Invite
  • DELETE /video-factory/comments/{comment_id} · scope media:writeDelete Comment
  • POST /video-factory/comments/{comment_id}:reopen · scope media:writeReopen Comment
  • POST /video-factory/comments/{comment_id}:resolve · scope media:writeResolve Comment
  • GET /video-factory/conforms/{cid} · scope media:readGet Conform
  • POST /video-factory/conforms/{cid}:apply · scope media:writeApply ConformTurn a read timeline into a new cut version - only when every clip in it is media this project already holds. The new cut carries `conformed_from` for the rest of its life, so any certificate issued from it says the edit was made outside Fitinty.
  • GET /video-factory/consent/{record_id}/trace · scope media:readTraceWhere has this person's voice ended up? Asked without changing anything.
  • POST /video-factory/consent/{record_id}/withdraw · scope media:writeWithdrawTake back what can be taken back, and say plainly what cannot.
  • GET /video-factory/continuity-reviews/{rid} · scope media:readGet Review
  • POST /video-factory/continuity-reviews/{rid}/decide · scope media:writeDecideA person telling the machine whether it was right. `disagreed` is a first-class outcome, not an override to be argued with - the model is wrong often enough that a review with no way to say so would just be a machine's opinion with extra steps.
  • GET /video-factory/cost/mine · scope media:readMy FootprintEverything you have asked this machine to do, across all your films. The number an operator would otherwise have to ask for, answerable by the person themselves - which is the point. Somebody who can see their own footprint can manage it; somebody who can only be told about it by an administrator finds out when it is already a conversation.
  • GET /video-factory/credits/{cid} · scope media:readGet Credits
  • GET /video-factory/credits/{cid}/video · scope media:readCredits Video
  • POST /video-factory/cuts/{cut_id}/handoff · scope media:writeCreate HandoffWrite the cut as a timeline an editor can open, and say plainly what did not come with it.
  • GET /video-factory/cuts/{cut_id}/handoffs · scope media:readList Handoffs
  • POST /video-factory/cuts/{cut_id}:render · scope media:writeRender CutSynchronous ffmpeg render of the EDL -> MP4 review export (+ WebVTT sidecar). Typically seconds for a few shots; the caller shows 'rendering'.
  • GET /video-factory/delivery-reports/{rid} · scope media:readGet Report
  • POST /video-factory/delivery-reports/{rid}/answer · scope media:writeAnswer`intended` is a real answer, not an override to be argued with. Most of what this report raises will turn out to be somebody's deliberate edit, and a report that treats every one of those as an outstanding failure is a report people stop opening.
  • PATCH /video-factory/entities/{eid} · scope media:writePatch Entity
  • DELETE /video-factory/entities/{eid} · scope media:writeDelete Entity
  • GET /video-factory/exports/{export_id}/accessibility · scope media:readCheck
  • POST /video-factory/exports/{export_id}/accessibility:issue · scope media:writeIssue
  • GET /video-factory/exports/{export_id}/captions · scope media:readExport Captions
  • GET /video-factory/exports/{export_id}/certificate · scope media:readGet Certificate
  • POST /video-factory/exports/{export_id}/credits · scope media:writeCreate
  • GET /video-factory/exports/{export_id}/credits · scope media:readList Credits
  • GET /video-factory/exports/{export_id}/credits:preview · scope media:readPreviewWhat the credits would say, before anything is rendered.
  • GET /video-factory/exports/{export_id}/deliverable · scope media:readWhat Would GoWhat a package for this export would contain, without building one. Worth its own route: the answer is a decision aid - "is this film ready to send?" - and asking it should not cost a zip of a film that is not ready to go anywhere yet.
  • GET /video-factory/exports/{export_id}/described-audio · scope media:readDescribed TrackThe descriptions as a WebVTT `descriptions` track - the standard way to ship audio description as text a screen reader can speak, without re-rendering the video.
  • GET /video-factory/exports/{export_id}/descriptions · scope media:readList DescriptionsEvery shot, with whatever description exists - and, for generated shots with none, what the shot was ASKED to be, offered as a starting point and labelled as such.
  • PUT /video-factory/exports/{export_id}/descriptions · scope media:writeSave DescriptionSaving a description marks it `human`: somebody has looked and written it. There is no path that promotes a suggestion to a description without a person passing it through here.
  • POST /video-factory/exports/{export_id}/loudness · scope media:writeNormaliseMake a rendition at a named delivery loudness. A NEW file - never a replacement.
  • POST /video-factory/exports/{export_id}/package · scope media:writeCreatePut everything that belongs with this film in one place, and say what does not.
  • GET /video-factory/exports/{export_id}/packages · scope media:readList Packages
  • GET /video-factory/exports/{export_id}/provenance/asset · scope media:readProvenance Asset
  • POST /video-factory/exports/{export_id}/provenance:issue · scope media:writeIssue Provenance
  • POST /video-factory/exports/{export_id}/qc · scope media:writeQc ExportThe video quality gate (measured, with fixes). Report, never a block.
  • GET /video-factory/exports/{export_id}/sound · scope media:readSound ReportEverything about this file's sound: how loud it is, where the speech is, where the silence is, and whether the audio description written for it can actually be heard.
  • POST /video-factory/exports/{export_id}/subtitles · scope media:writeCreateTranslate this film's captions into another language, and measure whether they can be read.
  • GET /video-factory/exports/{export_id}/subtitles · scope media:readList Tracks
  • POST /video-factory/exports/{export_id}/variants · scope media:writeExport VariantsCut the master into per-channel aspects (9:16 social, 1:1 feed, 4:5 portrait feed). Each variant is a real export row: its own QC, its own certificate (same EDL provenance).
  • GET /video-factory/exports/{export_id}/video · scope media:readExport Video
  • GET /video-factory/exports/{export_id}/withdrawn · scope media:readExport WithdrawnWhether this film contains material somebody has taken back, and what is waiting on a person.
  • POST /video-factory/exports/{export_id}/withdrawn-decision · scope media:writeDecideA person decides what happens to a finished film that holds withdrawn material. `destroy` really destroys the film file. `keep` does not un-flag anything - it records that a named person looked at this and chose to keep it, which is a different and more useful fact than the flag quietly disappearing.
  • GET /video-factory/exports/{to_export_id}/notes-from/{from_export_id} · scope media:readPreviewWhat the notes on the older cut point at now. Nothing is stored or moved.
  • POST /video-factory/exports/{to_export_id}/notes-from/{from_export_id} · scope media:writeIssueKeep the reading, so decisions about individual notes can be recorded against it.
  • PATCH /video-factory/footage/{fid} · scope media:writeUpdate Footage
  • DELETE /video-factory/footage/{fid} · scope media:writeDelete Footage
  • POST /video-factory/footage/{fid}/lipsync · scope media:writeRefuse Footage LipsyncA route that exists in order to refuse, and to say why in full. Returning 404 here would be cheaper and would teach nobody anything. Somebody who tries this is asking a reasonable-sounding question - "can I sync my own footage?" - and deserves the real answer rather than a dead end.
  • POST /video-factory/footage/{fid}/transcript · scope media:writeStart Transcript
  • GET /video-factory/footage/{fid}/transcript · scope media:readGet Transcript
  • PUT /video-factory/footage/{fid}/transcript · scope media:writeSave TranscriptCorrecting a line is what turns a guess into a record. `mark_checked` is the person saying they have actually read it - it is not set for them by editing one segment.
  • GET /video-factory/formats · scope media:readFormatsWhich format to pick, and the honest reason for each.
  • GET /video-factory/generations/{gen_id} · scope media:readGet Generation
  • GET /video-factory/generations/{gen_id}/thumbnail · scope media:readGeneration Thumbnail
  • GET /video-factory/generations/{gen_id}/video · scope media:readGeneration Video
  • POST /video-factory/generations/{gen_id}:cancel · scope media:writeCancel
  • POST /video-factory/generations/{gen_id}:confirm · scope media:writeConfirm
  • POST /video-factory/generations/{gen_id}:retry · scope media:writeRetryPlan MVF3 retry/recovery: a retryable or provider-unavailable take goes back to the queue with the SAME declaration (the person already made it) once the engine is reachable.
  • POST /video-factory/generations/{gid}/grade · scope media:writeStart GradeBring one shot closer to the rest of the film. Writes a new file; never touches the take.
  • POST /video-factory/generations/{gid}/lipsync · scope media:writeStart LipsyncRe-animate the mouth in this take to the lines spoken for its shot.
  • GET /video-factory/generations/{gid}/readings · scope media:readShot Readings
  • GET /video-factory/grades/{gid}/video · scope media:readGrade Video
  • GET /video-factory/handoffs/{hid}/file · scope media:readHandoff File
  • GET /video-factory/line-takes/{take_id}/audio · scope media:readTake Audio
  • GET /video-factory/lipsync/{lid}/video · scope media:readLipsync Video
  • DELETE /video-factory/locks/{lid} · scope media:writeUnlock Route
  • GET /video-factory/look-reports/{rid} · scope media:readGet Report
  • POST /video-factory/look-reports/{rid}/answer · scope media:writeAnswer"That shot is meant to look different" is the correct answer more often than not.
  • GET /video-factory/models · scope media:readList Models
  • POST /video-factory/note-carries/{cid}/decide · scope media:writeDecideCarry one note onto the new cut, drop it, or leave it where it was. `carry` writes a NEW comment on the new export rather than moving the old one - the original is what a reviewer actually wrote against a film they actually saw, and rewriting its timecode would make the record of that conversation untrue.
  • PUT /video-factory/organizations/{org_id}/machine-rate · scope media:writeSet RateSay what an hour of this machine is worth to you. Optional, and nothing is estimated in its absence. Recorded with a name because a figure that turns up in somebody's budget should be traceable to the person who chose it.
  • GET /video-factory/organizations/{org_id}/machine-rate · scope media:readGet Rate
  • GET /video-factory/organizations/{org_id}/stranded-projects · scope media:readStrandedWhich of this organisation's films are held by people who have left. The operational question nobody could ask before. Restricted to owners of the organisation: "whose work is stuck" is a management question, and it names departed colleagues.
  • GET /video-factory/packages/{pkg_id} · scope media:readGet Package
  • GET /video-factory/packages/{pkg_id}/download · scope media:readDownload
  • GET /video-factory/projects · scope media:readList Projects
  • POST /video-factory/projects · scope media:writeCreate Project
  • PUT /video-factory/projects/{pid}/adaptation · scope media:writeSave AdaptationThe person's edit: a new version authored 'human' (or 'mixed' after an AI draft).
  • POST /video-factory/projects/{pid}/adaptation:draft · scope media:writeDraft AdaptationLogline + treatment + beat sheet from the ingested source. A new adaptation version; the person edits it freely afterwards.
  • POST /video-factory/projects/{pid}/animatic · scope media:writeBuild AnimaticCuts the boarded panels to the planned durations. A shot with no panel becomes a plain card rather than being skipped - the shape of the film depends on that shot taking its time whether or not anybody drew it.
  • GET /video-factory/projects/{pid}/animatics · scope media:readList Animatics
  • GET /video-factory/projects/{pid}/bible · scope media:readGet Bible
  • PUT /video-factory/projects/{pid}/bible · scope media:writeSave BibleApply the change; the impact it had is recorded on the version, not just previewed.
  • POST /video-factory/projects/{pid}/bible:impact · scope media:writePreview Bible ImpactWhat this change would touch - BEFORE it propagates (plan 7.2). Read-only.
  • POST /video-factory/projects/{pid}/board · scope media:writeDraw Board
  • GET /video-factory/projects/{pid}/board · scope media:readGet Board
  • POST /video-factory/projects/{pid}/claim · scope media:writeClaimTake custody of a film whose maker has left. Only that - see the refusals.
  • GET /video-factory/projects/{pid}/composition · scope media:readCompositionWhat the latest cut actually contains - and therefore what the certificate is allowed to say.
  • POST /video-factory/projects/{pid}/conform · scope media:writeRead TimelineRead a timeline that came back from an editor. Nothing is applied - this says what is in it.
  • GET /video-factory/projects/{pid}/conforms · scope media:readList Conforms
  • GET /video-factory/projects/{pid}/continuity · scope media:readContinuity
  • POST /video-factory/projects/{pid}/continuity-review · scope media:writeStart ReviewSamples frames from every rendered take and asks a vision model what it sees. Off the request: three frames a shot on a twenty-shot film is minutes of GPU.
  • GET /video-factory/projects/{pid}/continuity-reviews · scope media:readList ReviewsA review a person cannot find again is a review that did not happen. Reloading the page must bring back the findings they had not decided on yet.
  • GET /video-factory/projects/{pid}/cost · scope media:readProject CostWhat this film has taken out of the machine.
  • GET /video-factory/projects/{pid}/custody · scope media:readCustodyWho controls this film, who made it, and whether either of those is a problem.
  • GET /video-factory/projects/{pid}/delivery · scope media:readPreviewThe film against its own script, recomputed now. Nothing is stored.
  • POST /video-factory/projects/{pid}/delivery · scope media:writeIssueKeep a dated copy, against the script version it was measured on - so a later rewrite cannot silently invalidate a report somebody has already read and acted on.
  • GET /video-factory/projects/{pid}/delivery-reports · scope media:readList Reports
  • GET /video-factory/projects/{pid}/entities · scope media:readList Entities
  • POST /video-factory/projects/{pid}/entities · scope media:writeAdd Entity
  • POST /video-factory/projects/{pid}/entities:extract · scope media:writeExtract EntitiesPull the characters, locations and props out of the source/treatment. Everything lands as DRAFT - a human approves each identity pack (and its rights) before shots may use it.
  • GET /video-factory/projects/{pid}/fit · scope media:readGet FitJust the fit report, for the panel that has to show it beside the shot list.
  • POST /video-factory/projects/{pid}/footage · scope media:writeIngest FootageBring in a clip somebody actually shot. What ffprobe finds beats what the uploader says, and the rights declaration travels with the file from here to the certificate.
  • GET /video-factory/projects/{pid}/footage · scope media:readList Footage
  • GET /video-factory/projects/{pid}/grades · scope media:readList Grades
  • GET /video-factory/projects/{pid}/graph · scope media:readProduction GraphThe whole graph in one call for the Story step.
  • POST /video-factory/projects/{pid}/handover · scope media:writeHandoverGive this film to somebody else. The ordinary path, taken while you are still here.
  • GET /video-factory/projects/{pid}/lipsync · scope media:readList Lipsync
  • POST /video-factory/projects/{pid}/locks · scope media:writeLock Route
  • POST /video-factory/projects/{pid}/look · scope media:writeStart ReportMeasure every shot in the cut and say which ones stand apart from the rest.
  • GET /video-factory/projects/{pid}/look-reports · scope media:readList Reports
  • GET /video-factory/projects/{pid}/note-carries · scope media:readList Carries
  • GET /video-factory/projects/{pid}/parts · scope media:readParts Route
  • POST /video-factory/projects/{pid}/parts/{part_id}:ai-edit · scope media:writeAi Edit Route
  • GET /video-factory/projects/{pid}/pitch.md · scope media:readPitch PackageThe one-document pitch a human hands to a producer: what it is, what it costs to render, who and what is in it, where the rights stand, and what is still open.
  • GET /video-factory/projects/{pid}/plan · scope media:readProduction Plan
  • POST /video-factory/projects/{pid}/presence · scope media:writePresence Route
  • GET /video-factory/projects/{pid}/scenes · scope media:readList Scenes
  • POST /video-factory/projects/{pid}/scenes · scope media:writeAdd Scene
  • POST /video-factory/projects/{pid}/scenes:breakdown · scope media:writeBreakdown ScenesTreatment + beats -> the scene list, each naming the entities it uses. Replaces DRAFT scenes only; approved scenes are never overwritten by the assistant.
  • GET /video-factory/projects/{pid}/score · scope media:readScore ReportDoes the music fit the film? Analyses the piece the first time it is asked about, and reuses that analysis for every cut afterwards - the phrase boundaries belong to the piece, not the cut.
  • POST /video-factory/projects/{pid}/score:listen · scope media:writeListenListen to the piece on this project's cut and find where it can end.
  • POST /video-factory/projects/{pid}/source:ingest · scope media:writeIngest SourceBring the source in behind the rights gate. A Publishers manuscript must be confirmed owned/licensed there, or the person declares the adaptation right here - either way the declaration is stored verbatim and quoted by the certificate.
  • POST /video-factory/projects/{pid}/speak · scope media:writeSpeak LinesSay every line in the script, in the voice cast for its speaker.
  • GET /video-factory/projects/{pid}/speech · scope media:readGet SpeechEvery line take, and the fit report - which is the part worth reading.
  • POST /video-factory/projects/{pid}/suggestions · scope media:writeSuggest Route
  • GET /video-factory/projects/{pid}/voices · scope media:readGet CastWho speaks in this film, who is cast, and who has nobody.
  • POST /video-factory/projects/{pid}/voices · scope media:writeCast Voice
  • POST /video-factory/projects/{pid}/voices-reconfirmed · scope media:writeReconfirm VoicesSay, by name, that you have checked the people who lent their voices still agree. A name and not a checkbox, for the same reason a subtitle track records who read it through: a box that somebody ticked is not a claim anybody made, and this one is about a real person's voice.
  • GET /video-factory/projects/{project_id} · scope media:readGet Project
  • DELETE /video-factory/projects/{project_id} · scope media:writeDelete Project
  • POST /video-factory/projects/{project_id}/approvals · scope media:writeRequest Approval
  • POST /video-factory/projects/{project_id}/brief · scope media:writeSave Brief
  • POST /video-factory/projects/{project_id}/collaborators · scope media:writeAdd Collaborator
  • GET /video-factory/projects/{project_id}/comments · scope media:readList Comments
  • POST /video-factory/projects/{project_id}/comments · scope media:writeAdd Comment
  • POST /video-factory/projects/{project_id}/cuts · scope media:writeSave Cut
  • POST /video-factory/projects/{project_id}/cuts:auto · scope media:writeAuto CutA first cut from the approved (or latest ready) take of every shot, in shot order.
  • POST /video-factory/projects/{project_id}/description:compile · scope media:writeCompile Description
  • POST /video-factory/projects/{project_id}/generations:estimate · scope media:writeEstimate
  • POST /video-factory/projects/{project_id}/moment:compile · scope media:writeCompile Moment
  • POST /video-factory/projects/{project_id}/script · scope media:writeSave Script
  • POST /video-factory/projects/{project_id}/script:draft · scope media:writeDraft Script
  • POST /video-factory/projects/{project_id}/share-links · scope media:writeCreate Share Link
  • GET /video-factory/projects/{project_id}/sharing · scope media:readGet Sharing
  • PATCH /video-factory/projects/{project_id}/sharing · scope media:writePatch Sharing
  • POST /video-factory/projects/{project_id}/shots · scope media:writeAdd Shot
  • POST /video-factory/projects/{project_id}/shots:plan · scope media:writePlan ShotsScript -> shot list with contracts. Replaces PLANNED shots that have no takes yet; shots that already rendered are kept (the person's work is never thrown away by a re-plan).
  • POST /video-factory/projects/{project_id}:recover · scope media:writeRecoverPlan MVF3 recovery. For every take of this project that an interrupted process left in preparing / generating / post_processing: if ComfyUI still has the job, leave it (the person can wait or cancel); if ComfyUI finished it and still holds the output, collect it; otherwise mark it failed_retryable with the reason, so Retry re-queues it under the same declaration.
  • POST /video-factory/projects/{project_id}:restore · scope media:writeRestore Project
  • POST /video-factory/projects/{project_id}:score-in-song-factory · scope media:writeScore In Song FactoryOpens a Song Factory project whose brief carries this video's music intent and whose video reference is the review export (SF6 measures cuts/energy and the Studio gets hit points).
  • POST /video-factory/projects/{project_id}:takedown · scope media:writeTakedown Project
  • GET /video-factory/queue · scope media:readRead QueueWhat the machine is doing, and where your work sits in it. Your own jobs come back in full. Everything else is a COUNT - how many are ahead of you is a number you need; whose they are and what they are for is not.
  • GET /video-factory/queue/history · scope media:readRead HistoryYour own finished jobs, with what they really cost.
  • GET /video-factory/queue/machine · scope media:readRead MachineThe card itself, and what each kind of work has really cost on it. Every number here is about the MACHINE, not about this caller - which is why it reads nothing tenant-scoped. Authentication is still required: what the Forge is holding and how long a render takes on it is not a public fact.
  • POST /video-factory/queue/{jid}/cancel · scope media:writeCancelLeave the line. A running job is not stopped from here - see the note.
  • POST /video-factory/reports · scope media:writeFile Report
  • GET /video-factory/reports · scope media:readList Reports
  • PATCH /video-factory/reports/{report_id} · scope media:writePatch Report
  • PATCH /video-factory/scenes/{sid} · scope media:writePatch Scene
  • DELETE /video-factory/scenes/{sid} · scope media:writeDelete Scene
  • GET /video-factory/search · scope media:readDo SearchFind a film by something inside it.
  • GET /video-factory/search/recent · scope media:readRecentYour own recent searches, and how long the live scan really took. The timing is the point: it is the number that will one day decide whether a pre-built index is worth the staleness it brings, and it should be measured by then rather than argued about.
  • DELETE /video-factory/share-links/{link_id} · scope media:writeRevoke Share Link
  • GET /video-factory/shared-with-me · scope media:readShared With Me
  • GET /video-factory/shot-readings/{reading_id}/frame · scope media:readReading FrameThe frame a finding came from. Without this route the report can say "check it yourself" and then give a person no way to look, which is worse than saying nothing.
  • PATCH /video-factory/shots/{shot_id} · scope media:writePatch Shot
  • DELETE /video-factory/shots/{shot_id} · scope media:writeDelete Shot
  • POST /video-factory/shots/{shot_id}/scene · scope media:writeAttach Shot To SceneComplete the graph: shot -> scene (and so -> entities, beats, source).
  • GET /video-factory/storage · scope media:readAuditWhat is really on the disk, what nothing points at, and what points at nothing.
  • GET /video-factory/storage/audits · scope media:readList Audits
  • POST /video-factory/storage/mark-ghosts · scope media:writeMark GhostsMake the rows tell the truth. Marks; never deletes. Only exports, generations and (since 567) voice takes carry a status this can be said in. The rest are reported and left exactly as they are: inventing a state on a table to record a fact about the disk would be a worse lie than the broken link. Voice takes were added because for them the broken link WAS the worse lie - a 'ready' take is what ev…
  • POST /video-factory/storage/sweep · scope media:writeSweepDelete the orphans, and only the orphans. `confirm_bytes` has to match what the audit said it would free. If the disk changed between looking and deciding, this refuses - somebody agreeing to free 40 MB has not agreed to free 4 GB, and a delete that silently took more than was shown would be unrecoverable.
  • GET /video-factory/subtitles/{tid} · scope media:readGet Track
  • POST /video-factory/subtitles/{tid}/reviewed · scope media:writeMark ReviewedRecord that somebody who speaks the language has been through it. Machine output and reviewed output are different things, and a track that cannot tell them apart will eventually be shipped as though a person had checked it.
  • GET /video-factory/subtitles/{tid}/vtt · scope media:readTrack Vtt
  • POST /video-factory/suggestions/{sid}:accept · scope media:writeAccept Route
  • POST /video-factory/suggestions/{sid}:reject · scope media:writeReject Route
  • DELETE /video-factory/voices/{cast_id} · scope media:writeUncast
  • GET /video-factory/withdrawals/{wid} · scope media:readGet Withdrawal
Cinema - 85 doors - cinema:read, cinema:write

Film and video production end to end, from script to a delivered package.

  • POST /cinema-payments/licenses · scope cinema:writeCreate License
  • GET /cinema-payments/licenses · scope cinema:readList Licenses
  • GET /cinema-payments/purchases/mine · scope cinema:readMy FilmsTHE VIEWER'S OWN FILMS (P5, 2026-09-24). A screening or a licence a person paid for, across every studio - the first customer-side view this pillar has had: until today a buyer held only the link the checkout returned. A purchase names its person by `created_by` when they were signed in and otherwise by the email they paid with - the standing ownership rule (own_identity), decided here per row on …
  • POST /cinema-payments/releases · scope cinema:writeCreate ReleaseMints the release for a DELIVERY-READY project with a completed render - the pipeline's own gate, not a parallel one. Born UNPUBLISHED: a film goes in front of the world by the toggle below, never by a row existing.
  • GET /cinema-payments/releases · scope cinema:readList Releases
  • PATCH /cinema-payments/releases/{release_id} · scope cinema:writeUpdate Release
  • POST /cinema-payments/releases/{release_id}/publish · scope cinema:writeToggle Release PublishThe pillar's go-live. PAY-TO-OPEN in the function body, off->on only - unpublishing is always the tenant's right.
  • POST /cinema-payments/webhook · scope cinema:writeCinema Stripe WebhookSignature or refusal, captured events only - education's shape exactly.
  • PATCH /cinema/adaptation-plans/{plan_id} · scope cinema:writeUpdate Adaptation Plan
  • POST /cinema/adaptation-plans/{plan_id}/breakdown-shots · scope cinema:writeBreakdown ShotsAI-drafts a real, structured scene-by-scene, shot-by-shot breakdown from an approved adaptation_plan's own beat_sheet, grounded strictly in that text - never invents a location/character/event beyond what the plan already states. Every drafted scene/shot lands as a real row via the exact same create_scene/create_shot functions the manual Scenes & Shots UI already uses (called in-process, matching …
  • GET /cinema/assets · scope cinema:readList Assets
  • POST /cinema/assets · scope cinema:writeUpload Asset
  • POST /cinema/assets/generate-concept-art · scope cinema:writeGenerate Concept ArtReal FLUX.1-dev text-to-image generation (flux_image_engine.py) - not a Pillow typography card. Blocks on the real ComfyUI job (a single FLUX frame is fast, ~10-20s on this hardware, unlike video), then stores the result as a real cinema_asset - same enhance_image() pipeline every other image upload in this platform goes through.
  • GET /cinema/assets/{asset_id}/download · scope cinema:readDownload Asset
  • GET /cinema/audio-tracks · scope cinema:readList Audio Tracks
  • POST /cinema/audio-tracks · scope cinema:writeCreate Audio Track
  • GET /cinema/available-manuscripts · scope cinema:readList Available ManuscriptsReal manuscripts the caller can actually see, via Publishers' own RLS - no separate cross-pillar permission check needed, the row-level policy on `manuscript` already scopes this correctly.
  • GET /cinema/avatar-projects · scope cinema:readList Avatar Projects
  • POST /cinema/avatar-projects · scope cinema:writeCreate Avatar Project
  • POST /cinema/character-looks · scope cinema:writeCreate Character LookA look is a named, reusable COMBINATION of items - assembled once by a director, applied to many shots, exactly the reuse the Emperor described ("should be able to change or add clothes... behave like they are living in real life") rather than re-picking every item on every single shot.
  • DELETE /cinema/character-looks/{look_id} · scope cinema:writeDelete Character Look
  • GET /cinema/characters · scope cinema:readList Characters
  • POST /cinema/characters · scope cinema:writeCreate Character
  • PATCH /cinema/characters/{character_id} · scope cinema:writeUpdate CharacterLinks a character to an already-uploaded reference photo (a real cinema_asset - honest reference/moodboard image for the art department, NOT an AI-video identity-lock mechanism, see migration 073's own note) and/or a voice consent record. RLS scopes both id checks below - a non-staff attempt to link an asset/consent from a different project simply won't find the row.
  • GET /cinema/characters/{character_id}/looks · scope cinema:readList Character Looks
  • GET /cinema/delivery-packages · scope cinema:readList Delivery Packages
  • POST /cinema/delivery-packages · scope cinema:writeCreate Delivery PackageDelivery readiness requires approval and validation (spec section 27) - real-enforced here: refuses to create a package until at least one review_cycle on this project has reached 'approved', matching the server-enforced gate pattern already proven for Publishers' production status gate and Education's certificate gate.
  • DELETE /cinema/dubs/{dub_id} · scope cinema:writeDelete Dub
  • GET /cinema/dubs/{dub_id}/artifact · scope cinema:readDownload Dub Artifact
  • PATCH /cinema/dubs/{dub_id}/mark-reviewed · scope cinema:writeMark Dub ReviewedA human explicitly confirming the dubbed draft has been reviewed - never automatic, matching manuscript_translation's exact same pattern (and for the same reason: MT quality plus an unverified accent- adaptation claim means this can never be treated as finished without a human actually watching/listening to it first).
  • GET /cinema/integrations · scope cinema:readList Integrations
  • GET /cinema/projects · scope cinema:readList Projects
  • POST /cinema/projects · scope cinema:writeCreate Project
  • GET /cinema/projects/{project_id} · scope cinema:readGet Project
  • GET /cinema/projects/{project_id}/adaptation-plans · scope cinema:readList Adaptation Plans
  • POST /cinema/projects/{project_id}/adaptation-plans/propose · scope cinema:writePropose Adaptation PlanBook-to-Movie bridge (spec section 10): pulls a real Publishers manuscript this project is linked to (title/synopsis/chapters) and asks the AI to draft a logline/synopsis/treatment/beat-sheet grounded in the actual book content - never invents plot beyond what the manuscript contains, and never touches canon or rights status. Lands as a draft adaptation_plan for a human to review/edit/approve - th…
  • PATCH /cinema/projects/{project_id}/status · scope cinema:writeTransition Project
  • GET /cinema/projects/{project_id}/voice-consent-records · scope cinema:readList Consent Records
  • POST /cinema/projects/{project_id}/voice-consent-records · scope cinema:writeCreate Consent RecordReal consent capture - typed full legal name + the exact statement agreed to, timestamped, plus an optional uploaded signed document. Whoever's voice is being cloned must be the one whose name and consent this record carries - a platform owner or project creator cannot consent on behalf of a real third person's voice, only for their own or with that person's own documented authorization.
  • GET /cinema/provenance · scope cinema:readList Provenance
  • POST /cinema/provenance · scope cinema:writeCreate ProvenanceSigning status stays 'unsigned' by default - real C2PA signing is a separately-gated real infrastructure decision (spec section 26/37), same discipline already used for Media/Publishers provenance.
  • GET /cinema/render-attempts/{attempt_id}/artifact · scope cinema:readDownload Artifact
  • POST /cinema/render-attempts/{attempt_id}/dubs · scope cinema:writeCreate Dub
  • GET /cinema/render-attempts/{attempt_id}/dubs · scope cinema:readList Dubs
  • GET /cinema/render-jobs · scope cinema:readList Render Jobs
  • POST /cinema/render-jobs · scope cinema:writeCreate Render Job
  • GET /cinema/render-jobs/{job_id}/attempts · scope cinema:readList Render Attempts
  • POST /cinema/render-jobs/{job_id}/run-assemble · scope cinema:writeRun Scene AssemblyReal ffmpeg-based timeline assembly (see cinema_video_engine.assemble_ scene_shots) - stitches a scene's already-COMPLETED shot renders into one continuous crossfaded video. This is the real answer to "Cinema generates individual clips but nothing stitches them together" - see CLAUDE.md's entry on the DaVinci Resolve question for the full real reasoning on why this is ffmpeg-based rather than Reso…
  • POST /cinema/render-jobs/{job_id}/run-mock · scope cinema:writeRun Mock RenderDrives a queued render job through a real mock render attempt - running -> completed (real ffmpeg-generated MP4 artifact) or failed (real ffmpeg error captured, retry-able by re-queuing the job).
  • POST /cinema/render-jobs/{job_id}/run-real · scope cinema:writeRun Real RenderDrives a queued render job through a REAL local AI video generation (ComfyUI + LTX-2.3-22B, running on The Forge's own GPU) - not the ffmpeg mock placeholder above. Same job/attempt lifecycle as run-mock; blocks synchronously while ComfyUI renders (a single 5s 720p clip took ~20-45s on this hardware during verification), matching the mock renderer's own synchronous shape rather than adding a separ…
  • PATCH /cinema/render-jobs/{job_id}/status · scope cinema:writeTransition Render Job
  • GET /cinema/reviews · scope cinema:readList Reviews
  • POST /cinema/reviews · scope cinema:writeCreate Review
  • PATCH /cinema/reviews/{review_id} · scope cinema:writeUpdate Review
  • GET /cinema/scenes · scope cinema:readList Scenes
  • POST /cinema/scenes · scope cinema:writeCreate Scene
  • POST /cinema/scenes/{scene_id}/batch-render · scope cinema:writeBatch Render SceneReal batch render: queues and runs one real render_job per shot in this scene, in shot-number order, consuming real Imperial Fuel credits from the linked Publisher house's real fuel account as it goes (the exact same balance-check + house_fuel_ledger-insert shape publishers_services_budget.py's own checkout_cart already uses). Honest, not all-or-nothing: if the house can't afford every shot, it re…
  • GET /cinema/scenes/{scene_id}/batch-render-cost-estimate · scope cinema:readBatch Render Cost EstimateReal, non-fabricated cost preview - genuinely computed from the scene's real current shot count times the real, documented per-shot fuel cost for the chosen engine (never a hash-based/random placeholder number). Also reports the house's real current fuel balance so a user can see up front whether they can afford it, without spending anything - the same "check before you commit" shape as tools_fuel…
  • GET /cinema/scenes/{scene_id}/continuity-check · scope cinema:readCheck Scene Wardrobe ContinuityThe real 'continuity department' feature the Emperor explicitly asked for ("actively track wardrobe continuity across a scene"). Fetches this scene's real shot-look assignments (RLS-scoped, same as every other read in this module) and runs the actual pure-logic checker from wardrobe_engine.py - see that module for the real, independently-verified algorithm.
  • GET /cinema/scenes/{scene_id}/shot-look-assignments · scope cinema:readList Scene Shot Look AssignmentsThe real current assignments for a scene's shots - what the continuity checker itself reads, exposed separately so the UI can show what's actually assigned, not just flagged issues.
  • GET /cinema/scripts · scope cinema:readList Scripts
  • POST /cinema/scripts · scope cinema:writeCreate Script
  • POST /cinema/scripts/{script_id}/versions · scope cinema:writeSave Script VersionSnapshots every scene's content under this script's project into one JSON version - same pattern as Publishers' manuscript_version.
  • POST /cinema/series-bibles/{bible_id}/movie-series · scope cinema:writePropose Movie SeriesSeries -> Movie series bridge. For every book in the series (Part 0) that doesn't already have a linked cinema_project, creates a new cinema_project (source_manuscript_id + series_bible_id set) via the exact same insert shape create_project already uses, then seeds its first adaptation_plan draft via the existing, unmodified propose_adaptation_plan - grounded in that specific book only, never a mi…
  • GET /cinema/series-bibles/{bible_id}/movie-series · scope cinema:readList Movie Series Projects
  • PUT /cinema/shot-look-assignments · scope cinema:writeSet Shot Look AssignmentUpsert on (shot_id, character_id) - a director re-assigning a character's look on a shot they already set replaces it, matching the real UNIQUE constraint on shot_look_assignment.
  • GET /cinema/shots · scope cinema:readList Shots
  • POST /cinema/shots · scope cinema:writeCreate Shot
  • POST /cinema/studios · scope cinema:writeCreate Studio
  • GET /cinema/studios/mine · scope cinema:readGet My Studio
  • GET /cinema/trailers · scope cinema:readList Trailers
  • POST /cinema/trailers · scope cinema:writeCreate Trailer
  • GET /cinema/voice-calibration-scripts · scope cinema:readList Calibration Scripts
  • GET /cinema/voice-consent-records/{record_id}/document · scope cinema:readDownload Consent Document
  • PATCH /cinema/voice-consent-records/{record_id}/revoke · scope cinema:writeRevoke Consent Record
  • GET /cinema/voice-consent-records/{record_id}/samples · scope cinema:readList Voice Samples
  • POST /cinema/voice-consent-records/{record_id}/samples · scope cinema:writeUpload Voice SampleA real microphone recording (or uploaded sound byte) of the person reading a calibration passage. Can ONLY be attached to a consent record that is still ACTIVE, checked here - the real mechanical enforcement of "no voice clone without live consent".
  • POST /cinema/voice-consent-records/{record_id}/synthesize · scope cinema:writeSynthesize NarrationReal DramaBox voice-cloned narration - the actual synthesis engine, gated the only way this platform ever gates voice cloning: an ACTIVE consent record and a real captured sample of THAT SAME consented person's voice, checked here, not just implied by the UI. Lands as a real cinema_asset (asset_type='audio') + an audio_track row, reusing the existing asset download path rather than a separate audi…
  • POST /cinema/voice-consent-records/{record_id}/train-voice-lora · scope cinema:writeTrain Voice LoraReal per-voice LoRA training (VoxCPM2) against every real captured sample for this consent record - higher fidelity than DramaBox's zero-shot cloning, confirmed via direct testing against a real recorded voice. Blocks synchronously while training runs (real GPU time, can take several minutes - the underlying node's own docs warn "this process takes time"), matching this platform's existing synchro…
  • DELETE /cinema/voice-samples/{sample_id} · scope cinema:writeDelete Voice Sample
  • GET /cinema/voice-samples/{sample_id}/file · scope cinema:readGet Voice Sample File
  • GET /cinema/wardrobe-items · scope cinema:readList Wardrobe Items
  • POST /cinema/wardrobe-items · scope cinema:writeCreate Wardrobe ItemItem source is real, per the Emperor's own explicit answer: both AI-generated (via wardrobe_engine's FLUX.1 call) and tenant-uploaded are supported side by side - `generate_reference_image=true` triggers the former, an actual `reference_file` upload is the latter. Neither is required; an item can exist as pure text with no reference image at all.
  • DELETE /cinema/wardrobe-items/{item_id} · scope cinema:writeDelete Wardrobe Item
  • GET /cinema/wardrobe-items/{item_id}/reference-image · scope cinema:readGet Wardrobe Item Image
Games - 73 doors - games:read, games:write

Game worlds, levels and assets, and the studios that build and sell them.

  • PATCH /games/assets/{asset_id} · scope games:writeReview Asset
  • GET /games/assets/{asset_id}/file · scope games:readDownload Asset File
  • POST /games/bundles · scope games:writeCreate BundleSeveral worlds, ONE store product (the Education mixed-bundle idea carried over). Every world must be the caller's own; the product rides the same marketplace machinery - and therefore marketplace's own pay-to-open gate.
  • GET /games/bundles · scope games:readList Bundles
  • POST /games/bundles/{bundle_id}/redeem · scope games:writeRedeem BundleThe single-world redeem's proof, verbatim: the REAL Medusa order must be THIS user's and must contain the bundle's product - then every bundled world grants its entitlement. Idempotent per world.
  • PATCH /games/choices/{choice_id} · scope games:writeEdit Choice
  • DELETE /games/choices/{choice_id} · scope games:writeDelete ChoiceRemoves the choice. What players already chose stays in their history - 228 snapshots the text at the moment of choosing and the history's link simply goes empty.
  • PATCH /games/compute-jobs/{job_id} · scope games:writeUpdate Compute Job
  • PATCH /games/entities/{entity_id} · scope games:writeReview Entity
  • GET /games/entities/{entity_id}/looks · scope games:readList Entity Looks
  • POST /games/entity-looks · scope games:writeCreate Entity Look
  • DELETE /games/entity-looks/{look_id} · scope games:writeDelete Entity Look
  • GET /games/hardware-telemetry · scope games:readGet Hardware TelemetryDeliberately synthetic - spec §22 locks real GPU/VRAM telemetry to a separate runtime approval this Core pass doesn't have. These numbers are representative placeholders, not a live reading of The Forge.
  • GET /games/integrations · scope games:readList IntegrationsWhat a studio can and cannot reach from this pillar, in its own words - never a list of product names Fitinty has not configured (LG15's walk, carried across).
  • GET /games/my/worlds · scope games:readMy WorldsTHE PLAYER'S OWN WORLDS (P5, 2026-09-24). Every world this account holds an entitlement to, with the order that granted it and the door that opens it - the first customer-side view this pillar has had: a buyer saw the store's order and nothing that said which world was theirs. Read on the PLAYER'S OWN cursor through the entitlement's self arm (260) - the wall answers who the caller is, and nothing…
  • POST /games/narrative-sources/{source_id}/extract · scope games:writeExtract Entities
  • POST /games/playtest/{token}/claim · scope games:writeClaim PlaytestA signed-in tester claims the SENT link; play opens for the invite's life. The claim insert runs on the system cursor (the tester is not an org member - the E8 token-as-credential posture), fenced by the live invite itself.
  • POST /games/playtest/{token}/feedback · scope games:writeLeave FeedbackThe tester's verdict lands on the studio's desk. Only a claimant of a live or recently-ended invite may file; one row per filing (a tester may file more than once as the build changes - each is a real reading).
  • POST /games/playtests · scope games:writeMint Playtest Invite
  • GET /games/playtests · scope games:readList PlaytestsThe desk: every invite with its claims and its feedback so far.
  • GET /games/playtests/feedback · scope games:readPlaytest Feedback
  • POST /games/playtests/{invite_id}/close · scope games:writeClose PlaytestEnds the playtest now: every claim stops opening the world the moment the invite expires. Feedback already filed stays - it was a real reading.
  • DELETE /games/playthroughs/{playthrough_id} · scope games:writeRestart Playthrough
  • POST /games/playthroughs/{playthrough_id}/choose · scope games:writeMake Choice
  • GET /games/playthroughs/{playthrough_id}/history · scope games:readPlaythrough History
  • GET /games/projects/{project_id} · scope games:readGet Project
  • PATCH /games/projects/{project_id} · scope games:writeUpdate Project
  • GET /games/projects/{project_id}/assets · scope games:readList Assets
  • POST /games/projects/{project_id}/assets · scope games:writeCreate Asset
  • GET /games/projects/{project_id}/compute-jobs · scope games:readList Compute Jobs
  • POST /games/projects/{project_id}/compute-jobs · scope games:writeCreate Compute Job
  • GET /games/projects/{project_id}/cross-pillar-links · scope games:readList Cross Pillar Links
  • POST /games/projects/{project_id}/cross-pillar-links · scope games:writeCreate Cross Pillar Link
  • GET /games/projects/{project_id}/entities · scope games:readList Entities
  • GET /games/projects/{project_id}/narrative-sources · scope games:readList Narrative Sources
  • POST /games/projects/{project_id}/narrative-sources · scope games:writeCreate Narrative Source
  • GET /games/projects/{project_id}/provenance · scope games:readList Provenance
  • POST /games/projects/{project_id}/provenance · scope games:writeCreate Provenance
  • GET /games/projects/{project_id}/worlds · scope games:readList Worlds
  • POST /games/projects/{project_id}/worlds · scope games:writeCreate World
  • PUT /games/scene-look-assignments · scope games:writeSet Scene Look AssignmentUpsert on (scene_definition_id, entity_id) - records which look an entity wears in a given scene's static preview, matching the real UNIQUE constraint on scene_look_assignment.
  • GET /games/scenes/{scene_definition_id}/look-assignments · scope games:readList Scene Look Assignments
  • POST /games/scenes/{scene_id}/generate-synthetic-preview · scope games:writeGenerate Synthetic Preview
  • GET /games/scenes/{scene_id}/level · scope games:readGet Level
  • POST /games/scenes/{scene_id}/level · scope games:writeWrite LevelA level written by a person, with no model involved - add its choices next.
  • PATCH /games/scenes/{scene_id}/level · scope games:writeEdit Level
  • POST /games/scenes/{scene_id}/level/choices · scope games:writeAdd Choice
  • POST /games/scenes/{scene_id}/level/generate · scope games:writeGenerate Level
  • POST /games/series-bibles/{bible_id}/game-series · scope games:writePropose Game SeriesSeries -> Game series bridge. For every book in the series (Part 0) that doesn't already have a linked game_project, creates a new game_project with the REAL series_bible_id/source_manuscript_id FK columns set (the fix migration 038's own comment deferred), then seeds it with a real narrative_source built from that specific book's own chapters and runs the existing, unmodified entity-extraction AI…
  • GET /games/series-bibles/{bible_id}/game-series · scope games:readList Game Series Projects
  • POST /games/studios · scope games:writeCreate Studio
  • GET /games/studios/mine · scope games:readGet My Studio Endpoint
  • GET /games/studios/{studio_id}/lore-voice-profiles · scope games:readList Lore Voice Profiles
  • POST /games/studios/{studio_id}/lore-voice-profiles · scope games:writeCreate Lore Voice Profile
  • GET /games/studios/{studio_id}/projects · scope games:readList Projects
  • POST /games/studios/{studio_id}/projects · scope games:writeCreate Project
  • GET /games/wardrobe-items · scope games:readList Wardrobe Items
  • POST /games/wardrobe-items · scope games:writeCreate Wardrobe Item
  • DELETE /games/wardrobe-items/{item_id} · scope games:writeDelete Wardrobe Item
  • GET /games/wardrobe-items/{item_id}/reference-image · scope games:readGet Wardrobe Item Image
  • GET /games/world-listings/by-product/{product_id} · scope games:readWorld Listing By ProductOrder page: 'this line item is a game world -> Play now'. Any signed-in user may look up which world a product unlocks (gwml_any_user_read); playing still requires the redeem step to prove the order is theirs.
  • PATCH /games/worlds/{world_id} · scope games:writeUpdate World
  • GET /games/worlds/{world_id}/access · scope games:readMy World AccessWhat the play UI needs in one call: can I play, is it for sale, at what product.
  • GET /games/worlds/{world_id}/leaderboard · scope games:readWorld LeaderboardREAL completed playthroughs, fastest first - finishing is worth something. Open to anyone who may play the world (member, buyer, or live playtester).
  • GET /games/worlds/{world_id}/level-map · scope games:readLevel MapThe studio's view of every level in the world, with what would stop a player finishing.
  • POST /games/worlds/{world_id}/marketplace-listing · scope games:writeList World On Marketplace
  • GET /games/worlds/{world_id}/marketplace-listing · scope games:readGet World ListingThe world page's 'Sell full access' panel state (null = not listed).
  • GET /games/worlds/{world_id}/playthrough · scope games:readMy Playthrough
  • POST /games/worlds/{world_id}/playthrough/start · scope games:writeStart Playthrough
  • POST /games/worlds/{world_id}/redeem · scope games:writeRedeem World PurchaseTurn a real purchase into play access. The order is fetched from Medusa with the admin token and checked: it must be THIS user's (email match) and must contain the world's listed product. Idempotent.
  • GET /games/worlds/{world_id}/scenes · scope games:readList Scenes
  • POST /games/worlds/{world_id}/scenes · scope games:writeCreate Scene
  • GET /games/worlds/{world_id}/wishlist · scope games:readWorld WishlistThe studio's own list, with conversion MEASURED: which wishers went on to own the world. Emails of wishers are the tenant's audience - exclusive, never resold; the conversion join is a count, not a surveillance feed.
Marketing - 143 doors - marketing:read, marketing:write

Campaigns, content and the scouting that feeds them.

  • GET /marketing/acquisition · scope marketing:readAcquisition GaugeTHE MACHINE'S GAUGE - the Emperor's screen. Every figure a measurement; platform-wide, so it answers only to the owner role.
  • GET /marketing/action-log · scope marketing:readList Action LogA LOG IS THE ONE LIST NOBODY PAGES BACK THROUGH, and the one most often read as complete - "that rule never fired" is a sentence people say about a log that simply stopped.
  • POST /marketing/action-log · scope marketing:writeRecord Action
  • POST /marketing/ad-bridge · scope marketing:writeCreate Ad Package
  • POST /marketing/ad-bridge/{pkg_id}/consent · scope marketing:writeConsent Ad PackageConsent is WORDS the human typed - what they agreed this campaign may spend per day - recorded on the row with their name (the G2 law for money).
  • POST /marketing/ad-bridge/{pkg_id}/export · scope marketing:writeExport Ad PackageThe hand-off: the package as plain text to paste into the ad platform.
  • POST /marketing/ad-bridge/{pkg_id}/launch · scope marketing:writeLaunch Ad Package
  • GET /marketing/ad-bridge/{pkg_id}/reading · scope marketing:readAd ReadingDeclared vs recorded, and what the closed loop can honestly say. Zeros are real; what cannot be measured is STATED, never estimated.
  • POST /marketing/ad-bridge/{pkg_id}/record-spend · scope marketing:writeRecord SpendBookkeeping of what YOU spent out there - a measurement of the past, never an instruction to spend. No money moves through this door.
  • POST /marketing/ads/spend-gate/authorize · scope marketing:writeAuthorize AdsHalf of the advertising lock. AD_SPEND_MODE, the profile's own gate, a cap above zero, the recorded consent words and a credential are the rest - this opens none of them.
  • POST /marketing/ads/spend-gate/revoke · scope marketing:writeRevoke Ads
  • GET /marketing/affiliate-compliance · scope marketing:readList Affiliate Disclosures
  • POST /marketing/affiliate-compliance · scope marketing:writeCreate Affiliate Disclosure
  • PATCH /marketing/affiliate-compliance/{disclosure_id}/approve · scope marketing:writeApprove Affiliate Disclosure
  • GET /marketing/affiliate-desk · scope marketing:readAffiliate DeskThe whole channel system on one read: every network's standing (credential state, last sync, latest reported money), the playbook's next action in words, offers with their real click counts, and what the agents have to work with.
  • POST /marketing/affiliate-desk/import-report · scope marketing:writeImport Report
  • PUT /marketing/affiliate-program · scope marketing:writeSet Affiliate Program
  • POST /marketing/affiliate/accrue · scope marketing:writeAccrue Affiliate EarningsRead-through minting: one earning per REAL billing event of an org whose member was referred, idempotent by event id. No daemon - the Emperor's hand (or the screen that shows him the desk) runs it.
  • GET /marketing/affiliate/earnings · scope marketing:readMy Affiliate EarningsThe affiliate's own desk (RLS scopes: owners see all, others see theirs).
  • POST /marketing/affiliate/earnings/{earning_id}/record-payment · scope marketing:writeRecord Affiliate PaymentThe Rule-3 wall, publishers-royalty shape: a human paid outside; this door records that fact and nothing else.
  • POST /marketing/agent-runs · scope marketing:writeRun Multi Agent Amp
  • GET /marketing/agent-runs · scope marketing:readList Agent Runs
  • GET /marketing/attribution · scope marketing:readAttribution
  • GET /marketing/audience · scope marketing:readMy AudienceThe seller's book: counts and the latest members. A None count is a dead read, never a confident zero.
  • GET /marketing/audience-segments · scope marketing:readList Audience Segments
  • POST /marketing/audience-segments · scope marketing:writeCreate Audience Segment
  • DELETE /marketing/audience/{member_id} · scope marketing:writeRemove MemberThe removal right: hard delete, because a person asking to be forgotten is not asking to be archived.
  • GET /marketing/brand-listening · scope marketing:readList Brand Mentions
  • POST /marketing/brand-listening · scope marketing:writeCreate Brand Mention
  • POST /marketing/brand-listening/{mention_id}/draft-response · scope marketing:writeDraft ResponseAI drafts a recommended response only - never posted automatically, matching this project's 'AI drafts, never authoritative' pattern used everywhere else.
  • POST /marketing/campaign-plan · scope marketing:writeAdd Plan Item
  • POST /marketing/campaign-plan/items/{item_id}/status · scope marketing:writeSet Plan Item Status
  • GET /marketing/campaign-plan/{campaign_id} · scope marketing:readCampaign Plan
  • GET /marketing/campaigns · scope marketing:readList Campaigns
  • POST /marketing/campaigns · scope marketing:writeCreate Campaign
  • GET /marketing/campaigns/{campaign_id} · scope marketing:readGet Campaign
  • PATCH /marketing/campaigns/{campaign_id}/status · scope marketing:writeSet Campaign Status
  • GET /marketing/capability-gates · scope marketing:readList Gates
  • POST /marketing/capability-gates/relock · scope marketing:writeRelock Gate
  • POST /marketing/capability-gates/unlock · scope marketing:writeUnlock Gate
  • POST /marketing/content-factory · scope marketing:writeGenerate Content
  • GET /marketing/content-factory · scope marketing:readList Content Assets
  • GET /marketing/content-factory/{asset_id}/image · scope marketing:readDownload Content Image
  • PATCH /marketing/content-factory/{asset_id}/status · scope marketing:writeSet Content Status
  • GET /marketing/cross-pillar-links · scope marketing:readList Cross Pillar Links
  • POST /marketing/cross-pillar-links · scope marketing:writeCreate Cross Pillar Link
  • GET /marketing/email/drafts · scope marketing:readList Email Drafts
  • POST /marketing/email/drafts · scope marketing:writeCreate Email Draft
  • PATCH /marketing/email/drafts/{draft_id}/status · scope marketing:writeSet Email Draft Status
  • POST /marketing/forge/friction · scope marketing:writeCreate Friction
  • GET /marketing/forge/friction · scope marketing:readList Friction
  • POST /marketing/forge/friction/{friction_id}/generate-solution · scope marketing:writeGenerate Solution
  • GET /marketing/forge/solutions · scope marketing:readList Solutions
  • POST /marketing/forge/solutions/{solution_id}/match · scope marketing:writeMatch Solution
  • GET /marketing/forge/solutions/{solution_id}/matches · scope marketing:readList Matches
  • POST /marketing/forge/vassals · scope marketing:writeCreate Vassal
  • GET /marketing/forge/vassals · scope marketing:readList Vassals
  • GET /marketing/frequency-caps · scope marketing:readList Frequency Caps
  • POST /marketing/frequency-caps · scope marketing:writeSet Frequency Cap
  • POST /marketing/gated-execution · scope marketing:writeRequest Gated ExecutionThe real enforcement point: checks the gate's actual current status and either blocks (real, not decorative) or produces a real simulated outcome. Every attempt - blocked or simulated - lands as a real, permanent audit row, regardless of outcome.
  • GET /marketing/gated-execution · scope marketing:readList Gated Executions
  • GET /marketing/growth-command · scope marketing:readGrowth Command
  • GET /marketing/guardrails · scope marketing:readList Guardrails
  • POST /marketing/guardrails · scope marketing:writePropose GuardrailEvery guardrail is versioned - a new proposal is a new row with an incrementing version, matching Tools' own resource_policy pattern.
  • PATCH /marketing/guardrails/{guardrail_id}/toggle · scope marketing:writeToggle Guardrail
  • GET /marketing/hook-lab/tests · scope marketing:readList Hook Tests
  • POST /marketing/hook-lab/tests · scope marketing:writeCreate Hook Test
  • POST /marketing/hook-lab/tests/{hook_test_id}/complete · scope marketing:writeComplete Hook TestDeclares a winner by real conversion-rate comparison, refusing to declare one from insufficient data - identical discipline to Media's own Hook Lab experiment completion.
  • POST /marketing/hook-lab/tests/{hook_test_id}/start · scope marketing:writeStart Hook Test
  • GET /marketing/hook-lab/tests/{hook_test_id}/variants · scope marketing:readList Hook Variants
  • POST /marketing/hook-lab/variants · scope marketing:writeCreate Hook Variant
  • PATCH /marketing/hook-lab/variants/{variant_id}/record · scope marketing:writeRecord Variant Metrics
  • GET /marketing/integrations · scope marketing:readList Integrations
  • POST /marketing/knowledge-chunks · scope marketing:writeCreate Knowledge Chunk
  • GET /marketing/knowledge-chunks · scope marketing:readList Knowledge Chunks
  • POST /marketing/knowledge-chunks/search · scope marketing:writeSemantic SearchReal pgvector cosine-distance search - never a keyword LIKE match dressed up as 'AI search'. Grounds the multi-agent AMP core in real stored facts.
  • GET /marketing/loyalty/balances · scope marketing:readLoyalty BalancesEvery customer's points, COMPUTED from captured money on the rails at read minus recorded redemptions - a mirror of money drifts, a computation cannot.
  • PUT /marketing/loyalty/program · scope marketing:writePut Program
  • POST /marketing/loyalty/redeem · scope marketing:writeRecord RedemptionThe tenant hands over a reward and RECORDS it - never automatic, never a charge; over-redemption is refused against the computed balance.
  • POST /marketing/mail/drain · scope marketing:writeDrain
  • POST /marketing/mail/nurture/enroll · scope marketing:writeEnroll Nurture
  • POST /marketing/mail/queue · scope marketing:writeQueue CampaignExpand an APPROVED draft against the org's consented audience. Rows are born 'held' - the mail_outbox law - and nothing transmits here. Unsubscribed and opted-out addresses are never minted a row; the response says how many.
  • GET /marketing/mail/sends · scope marketing:readList SendsThe truth of one draft's sends - counts by status, never dressed up.
  • GET /marketing/migration-plans · scope marketing:readList Migration Plans
  • POST /marketing/migration-plans · scope marketing:writeCreate Migration Plan
  • GET /marketing/narrative-foundational-template · scope marketing:readFoundational TemplateThe CORE_DECREE's own foundational-narrative text, to pre-fill a new source.
  • GET /marketing/narrative-seeds · scope marketing:readList Seeds
  • POST /marketing/narrative-seeds/generate · scope marketing:writeGenerate Seed
  • POST /marketing/narrative-sources · scope marketing:writeCreate Source
  • GET /marketing/narrative-sources · scope marketing:readList Sources
  • PATCH /marketing/narrative-sources/{source_id} · scope marketing:writeUpdate Source
  • GET /marketing/objection-dojo/scenarios · scope marketing:readList Objection Scenarios
  • POST /marketing/objection-dojo/scenarios · scope marketing:writeCreate Objection Scenario
  • PATCH /marketing/objection-dojo/scenarios/{scenario_id}/review · scope marketing:writeReview Objection Scenario
  • GET /marketing/pioneer-sequences · scope marketing:readList Pioneer Sequences
  • POST /marketing/pioneer-sequences · scope marketing:writeCreate Pioneer Sequence
  • GET /marketing/playbooks · scope marketing:readList Playbooks
  • POST /marketing/playbooks · scope marketing:writeCreate Playbook
  • PATCH /marketing/playbooks/{playbook_id}/status · scope marketing:writeSet Playbook Status
  • PUT /marketing/press-kit · scope marketing:writeUpdate Press Kit
  • POST /marketing/profile · scope marketing:writeCreate Profile
  • GET /marketing/profile/mine · scope marketing:readGet My Profile Endpoint
  • GET /marketing/profile/{profile_id}/members · scope marketing:readList Members
  • POST /marketing/profile/{profile_id}/members · scope marketing:writeAdd Member
  • GET /marketing/provenance · scope marketing:readList Provenance Records
  • POST /marketing/provenance · scope marketing:writeCreate Provenance Record
  • GET /marketing/routing-policies · scope marketing:readList Routing Policies
  • POST /marketing/routing-policies · scope marketing:writeCreate Routing PolicyLocal-first and zero-recurring-cost policies remain preferred where feasible (spec section 25) - no model/external API is activated without review, so every new policy lands unapproved until a separate approve call, matching Tools' own resource_policy approval gate.
  • POST /marketing/routing-policies/{policy_id}/approve · scope marketing:writeApprove Routing Policy
  • GET /marketing/scout/hypotheses · scope marketing:readList Trend Hypotheses
  • POST /marketing/scout/hypotheses · scope marketing:writeCreate Trend Hypothesis
  • GET /marketing/scout/observations · scope marketing:readList Scout Observations
  • POST /marketing/scout/observations · scope marketing:writeCreate Scout Observation
  • GET /marketing/scout/sources · scope marketing:readList Scout Sources
  • POST /marketing/scout/sources · scope marketing:writeCreate Scout Source
  • GET /marketing/seo-geo/briefs · scope marketing:readList Landing Page Briefs
  • POST /marketing/seo-geo/briefs · scope marketing:writeCreate Landing Page Brief
  • POST /marketing/seo-geo/briefs/{brief_id}/generate · scope marketing:writeGenerate Landing PageReal AI-drafted pSEO/GEO content: page copy + real schema.org JSON-LD structured data, grounded in the real knowledge base (Research, reused from the Content Factory pass) and the brief's own linked topic cluster. Never auto-published - lands as a draft the same as every other AI- generated artifact in this pillar; rendering it as a real, public, crawlable page is a separate, deliberately-locked s…
  • GET /marketing/seo-geo/clusters · scope marketing:readList Seo Clusters
  • POST /marketing/seo-geo/clusters · scope marketing:writeCreate Seo Cluster
  • GET /marketing/seo-geo/storefront-pages · scope marketing:readStorefront Seo PagesTHE SEO PREVIEW READS THE REAL STOREFRONT (2026-09-11) - the profile's storefront as a search engine reads it (`storefront_seo.reading`). It takes NO address: the pages are the sitemap's own list for this profile's business, so nobody can point the reader at anything else.
  • GET /marketing/social-accounts · scope marketing:readList Accounts
  • POST /marketing/social-accounts/manual · scope marketing:writeConnect ManualPaste-a-token: the road that works TODAY, before any OAuth app review. The token is encrypted the moment it arrives and never travels back out.
  • GET /marketing/social-accounts/oauth/{network}/start · scope marketing:readOauth Start
  • GET /marketing/social-accounts/summary · scope marketing:readFooter SummaryWhat the dashboard footer shows: the caller's orgs' connected networks and footer links, plus the connect door's path.
  • DELETE /marketing/social-accounts/{account_id} · scope marketing:writeDisconnect
  • PUT /marketing/social-autopilot · scope marketing:writeSet Autopilot
  • PUT /marketing/social-footer · scope marketing:writeSet Footer LinksThe org's footer row, replaced wholesale - what you save is exactly what every footer prints.
  • POST /marketing/social-posts/{post_id}/mark-posted · scope marketing:writeMark Posted
  • POST /marketing/social-posts/{post_id}/transmit · scope marketing:writeTransmit Social PostThe machine's posting hand - which exists now, and answers to three locks. With any lock shut it refuses in plain words and posts NOTHING; mark-posted (the human hand) remains the everyday road.
  • GET /marketing/social-queue · scope marketing:readList Social Posts
  • POST /marketing/social-queue · scope marketing:writeCreate Social Post
  • PATCH /marketing/social-queue/{post_id}/status · scope marketing:writeSet Social Post Status
  • POST /marketing/social/send-gate/authorize · scope marketing:writeAuthorize SocialHalf of the social lock. SOCIAL_POST_MODE is the other half and this does not touch it.
  • POST /marketing/social/send-gate/revoke · scope marketing:writeRevoke Social
  • POST /marketing/social/transmit-due · scope marketing:writeTransmit DueTHE AGENTS' POSTING HAND - drain-shaped, never a timer in this codebase (wiring it to the droplet's doctor timer is a launch-checklist line that waits for the gate to open anyway). Same three locks as the transmit door; scope is the CALLER'S orgs by RLS, each org needing autopilot AND its outreach gate.
  • GET /marketing/strategy · scope marketing:readGet Strategy
  • PUT /marketing/strategy · scope marketing:writePut Strategy
  • POST /marketing/testimonials · scope marketing:writeSubmit TestimonialA tenant offers words about Fitinty. The consent words - what THEY agreed to, e.g. that this may appear publicly with their name - are recorded on the row, the G2 law. Nothing shows publicly until the Emperor approves.
  • POST /marketing/testimonials/{t_id}/approve · scope marketing:writeApprove Testimonial
  • POST /tenant-referrals · scope marketing:writeMint CodeAny signed-in person may carry a Fitinty link - tenants recommending the platform ARE the platform's oldest acquisition channel.
  • GET /tenant-referrals · scope marketing:readMy CodesThe referrer's desk: counts only, never people - who signed up is not the referrer's to know. Conversion = the referred user now holds a PAID or FOUNDING org, computed at read on the system cursor.
  • POST /tenant-referrals/claim · scope marketing:writeClaim ReferralThe signup's one-time claim, called by the app shell after a referred signup. UNIQUE(user_id) makes attribution a decision, not an argument; a user who already claimed (or was never referred) is a quiet no-op.
Tools - 52 doors - tools:read, tools:write

Shared machinery: compute, translation, rendering and the fuel they burn.

  • GET /tools/alert-rules · scope tools:readList Alert Rules
  • POST /tools/alert-rules · scope tools:writeCreate Alert Rule
  • PATCH /tools/alert-rules/{rule_id} · scope tools:writeUpdate Alert Rule
  • GET /tools/alerts · scope tools:readList Alerts
  • POST /tools/alerts/{alert_id}/acknowledge · scope tools:writeAcknowledge Alert
  • GET /tools/backups · scope tools:readList Backups
  • POST /tools/backups · scope tools:writeRecord Backup
  • POST /tools/backups/{backup_id}/verify-restore · scope tools:writeVerify RestoreA restore-TEST record, not an actual restore execution - marks the backup as proven recoverable. Matches spec section 23: 'A backup is not accepted until restore is tested.'
  • GET /tools/cross-pillar-map · scope tools:readCross Pillar MapThe spec asks Tools to visualize demand from Cinema, Media, Education, Investors, Services, Offices, Publishers, Marketplace, Manufacturing, Logistics, Farms - none of those pillars produce a real compute-demand signal yet (no job queue of their own feeds into this platform's queue_job table), so this reports real BUILD status per pillar (built vs not-yet-built) rather than fabricating demand perc…
  • GET /tools/fabrication · scope tools:readList Fabrication Projects
  • POST /tools/fabrication · scope tools:writeCreate Fabrication Project
  • GET /tools/fuel/account · scope tools:readGet AccountGet-or-create the caller's fuel account (grants 50 Pioneer credits on first touch).
  • GET /tools/fuel/accounts · scope tools:readList Accounts
  • POST /tools/fuel/check · scope tools:writeCheck FuelThe 'no free labor' pre-flight gate: enough Credit_Balance for this task? No debit.
  • POST /tools/fuel/consume · scope tools:writeConsume FuelDebit fuel for an authorized heavy-compute task. Blocks (402) if insufficient.
  • GET /tools/fuel/cost-table · scope tools:readCost Table
  • POST /tools/fuel/grant · scope tools:writeGrant Fuel
  • GET /tools/fuel/ledger · scope tools:readGet Ledger
  • GET /tools/idle-factory/simulations · scope tools:readList Idle Factory Simulations
  • POST /tools/idle-factory/simulations · scope tools:writeRun Idle Factory SimulationReal logic against the platform's own latest approved resource policy: available capacity is 100% minus the policy's reserved_pct (falls back to a conservative 30% reserve if no policy is approved yet). A job that would exceed available capacity is preempted, never silently allowed - matching the spec's own preemption-order rule (section 19: no rental workload outranks platform safety).
  • GET /tools/integrations · scope tools:readList Integrations
  • GET /tools/inventory · scope tools:readList Inventory
  • POST /tools/inventory · scope tools:writeCreate Inventory Item
  • POST /tools/inventory/forecasts · scope tools:writeCreate ForecastA real, deterministic forecast rule (not a hand-picked number): project 30 days of usage from the gap between on_hand and reorder_level, propose replenishment only if the projected level would fall below the reorder point. Confidence is fixed at 0.5 (synthetic-data honesty, not a real ML forecast) until a real usage-history model exists.
  • GET /tools/inventory/{inventory_item_id}/forecasts · scope tools:readList Forecasts
  • GET /tools/maintenance · scope tools:readList Maintenance
  • POST /tools/maintenance · scope tools:writePropose Maintenance
  • POST /tools/maintenance/{task_id}/approve · scope tools:writeApprove Maintenance
  • GET /tools/omniforge/projects · scope tools:readList Omniforge Projects
  • POST /tools/omniforge/projects · scope tools:writeCreate Omniforge Project
  • GET /tools/policy · scope tools:readList Policies
  • POST /tools/policy · scope tools:writePropose PolicyNo production policy is activated until benchmarked and approved (spec section 10) - a proposed policy always lands unapproved; use the separate approve endpoint to activate it. This is the same propose-then-approve shape as maintenance_task below.
  • POST /tools/policy/{policy_id}/approve · scope tools:writeApprove Policy
  • GET /tools/queue · scope tools:readList Queue
  • POST /tools/queue/synthetic · scope tools:writeCreate Synthetic JobEvery job created here is explicitly synthetic - no real compute is ever dispatched, matching the spec's own `running_mock`/`completed_mock` status naming (section 11).
  • PATCH /tools/queue/{job_id} · scope tools:writeTransition Job
  • GET /tools/registry · scope tools:readList Tools
  • POST /tools/registry · scope tools:writeRegister Tool
  • DELETE /tools/registry/{tool_id} · scope tools:writeDelete Tool
  • POST /tools/render-calibration · scope tools:writeRecord CalibrationOne REAL measured run - the controlled test's own numbers, by the owner's hand. The note must say what was rendered; a calibration row with no story is a number nobody can audit.
  • GET /tools/render-calibration · scope tools:readList Calibration
  • GET /tools/render-calibration/estimate · scope tools:readEstimateThe Emperor's pricing arithmetic: what a feature of this length would burn at the measured median rate, and what that burn costs at the pack shelf's own per-minute prices. Labeled for what it is - a cost basis, not advice.
  • GET /tools/resource-estimates · scope tools:readList Estimates
  • POST /tools/resource-estimates · scope tools:writeCreate Estimate
  • GET /tools/security-policies · scope tools:readList Security Policies
  • POST /tools/security-policies · scope tools:writeCreate Security Policy
  • GET /tools/telemetry/history · scope tools:readTelemetry History
  • GET /tools/telemetry/latest · scope tools:readCapture TelemetryCaptures a fresh sample on every call rather than serving a cached row - telemetry is meant to be live. Real GPU read (nvidia-smi) and real host read (psutil) are attempted independently and merged; whatever can't be read for real falls back to clearly-labeled synthetic values for that field only, and `is_synthetic` is true only if EVERY field came from the synthetic generator (a partial real read…
  • GET /tools/vram-reserve · scope tools:readVram Reserve StateLive Sovereign VRAM Reserve + thermal state (read-only monitoring).
  • POST /tools/vram-reserve/admit · scope tools:writeVram Reserve AdmitAdmission control: would allocating `requested_gb` for a `lane`-class task breach the Sovereign VRAM Reserve? Returns the honest decision + math. 'omega' seizure is owner-only (L426).
  • POST /tools/vram-reserve/enforce · scope tools:writeVram Reserve EnforceRun one enforcement pass (owner/Emperor authority). Reads live state; if thermally throttling or in VRAM Imperial-Red, pauses non-critical SYNTHETIC queue jobs (running_mock -> paused) - never real GPU processes - and records a durable vram_reserve_event either way (the decree's audit trail; a RED-FLAG event if the reserve was breached).
  • GET /tools/vram-reserve/events · scope tools:readVram Reserve Events
Offices - 125 doors - offices:read, offices:write

Places of business: domains, delivery and the operations behind an office.

  • GET /offices · scope offices:readList Offices
  • POST /offices · scope offices:writeCreate Office
  • POST /offices/agreement-templates · scope offices:writeCreate Agreement Template
  • POST /offices/agreements · scope offices:writeCreate Agreement
  • POST /offices/agreements/{agreement_id}/assemble · scope offices:writeAssemble AgreementFills the template's placeholders from the RECORD (practice, client, terms) and the practice's answers. What cannot be resolved stays a placeholder and is LISTED - never guessed. Draft agreements only. Metered: Try refused in words, one use per document.
  • POST /offices/agreements/{agreement_id}/balance · scope offices:writePay Balance
  • POST /offices/agreements/{agreement_id}/deposit · scope offices:writePay Deposit
  • POST /offices/agreements/{agreement_id}/deposit-terms · scope offices:writeSet Deposit TermsThe practice's dial: "30% to start, the balance on completion" is 3000 bps.
  • POST /offices/agreements/{agreement_id}/instalments · scope offices:writeSchedule InstalmentsThe practice's hand schedules N monthly parts - each a pending row with its own pay door; the agreement funds when the LAST one captures. Refused once any money moved.
  • POST /offices/agreements/{agreement_id}/pay · scope offices:writeStart Agreement CheckoutThe CLIENT'S pay door on their own agreement. Stripe hosts the page; the client pays the PRACTICE; Fitinty's cut rides Stripe's own split. One purchase per agreement - a cancelled checkout leaves a pending row this door REUSES rather than doubles.
  • GET /offices/agreements/{agreement_id}/payments · scope offices:readAgreement PaymentsEvery part on the rail for one agreement - the practice or the named client.
  • POST /offices/agreements/{agreement_id}/set-terms · scope offices:writeSet Agreement TermsThe practice names the price and the client - the two facts the pay door charges by. amount_placeholder stays untouched display history.
  • PATCH /offices/agreements/{agreement_id}/status · scope offices:writeSet Agreement Status
  • POST /offices/bookings · scope offices:writeCreate Booking
  • GET /offices/bookings/mine · scope offices:readList My Bookings
  • POST /offices/bookings/{booking_id}/pay · scope offices:writePay ConsultationOF8 gap 1 - THE CONSULTATION PAY DOOR. A priced consultation type was stored and shown and never charged. One more KIND on the one rail: client-only, the booking's own price (read from its consultation type at the moment of paying), pending-before-Stripe on the practice's account, reused never doubled, flipped by the one webhook - and a paid consultation CONFIRMS a requested booking (the flip says…
  • PATCH /offices/bookings/{booking_id}/status · scope offices:writeSet Booking Status
  • GET /offices/client-workspaces/{workspace_id}/journey · scope offices:readGet Client JourneyReal Customer Journey Viewer (Workspace tools manifest id 60) - stitches every real touchpoint a client_workspace already has (intake, bookings, agreements, projects, deliverables, delivery reviews) into one chronological timeline. No new schema - every row here is real data already produced by an existing Offices flow, just never presented as a single view before. Relies entirely on each source t…
  • POST /offices/consultations · scope offices:writeCreate Consultation
  • PATCH /offices/consultations/{session_id} · scope offices:writeUpdate Consultation
  • POST /offices/deliverables · scope offices:writeUpload Deliverable
  • GET /offices/deliverables/{deliverable_id}/download · scope offices:readDownload Deliverable
  • GET /offices/deliverables/{deliverable_id}/reviews · scope offices:readList Delivery Reviews
  • POST /offices/delivery-reviews · scope offices:writeCreate Delivery ReviewAlways a real human reviewer_user_id (the calling user) - never an AI decision, matching the spec's explicit "AI cannot approve its own output" requirement.
  • GET /offices/documents/{document_id}/download · scope offices:readDownload Document
  • PATCH /offices/documents/{document_id}/sharing · scope offices:writeSet Document SharingThe practice's hand: a document attached to a room is shared unless it says otherwise.
  • POST /offices/handoff-packages · scope offices:writeCreate Handoff Package
  • POST /offices/intake-forms · scope offices:writeCreate Intake Form
  • POST /offices/intake-submissions · scope offices:writeCreate Intake Submission
  • PATCH /offices/intake-submissions/{submission_id}/status · scope offices:writeSet Intake Status
  • GET /offices/integrations · scope offices:readList Integrations
  • GET /offices/intel · scope offices:readPractice Intel ReportFitinty's metered instrument: one report, one use. Try refused in words.
  • GET /offices/invoices/mine · scope offices:readMy InvoicesThe client's own invoices across every practice, with their pay doors.
  • POST /offices/invoices/{invoice_id}/pay · scope offices:writePay InvoiceThe client's pay door on their own invoice - a pending part on the ONE rail, reused never doubled; the webhook flips bookkeeping's row to paid.
  • POST /offices/leads · scope offices:writeAdd Manual Lead
  • GET /offices/leads · scope offices:readList LeadsThe desk: every lead with its DERIVED band and reasons, plus conversion MEASURED from what really happened - never estimated.
  • GET /offices/leads/reading · scope offices:readLead ReadingConversion by source and by response speed - measured from the desk's own rows.
  • POST /offices/leads/sync-intakes · scope offices:writeSync Intake LeadsTHE ONE MINT DOOR from intakes: every submission becomes a lead exactly once (unique on source_ref), fenced to the caller's own practice. A repeat press mints nothing and says so.
  • POST /offices/leads/{lead_id}/contacted · scope offices:writeMark Lead ContactedTHE RESPONSE CLOCK stops here, once: the first time the practice reached out. A second press changes nothing - the first contact is the one measured.
  • POST /offices/leads/{lead_id}/merge-into/{survivor_id} · scope offices:writeMerge LeadThe practice's HAND merges a duplicate: the merged lead closes with the reason written, points at the survivor, and is excluded from every count. Nothing is deleted, nothing is merged by a machine.
  • POST /offices/leads/{lead_id}/refer-to/{office_id} · scope offices:writeRefer Lead OutA practice passes a lead it cannot serve to another Fitinty practice, with the client's recorded consent words. Once per (lead, practice). The lead itself stays where it is.
  • GET /offices/leads/{lead_id}/screen · scope offices:readLead ScreenOne lead, fully vetted: the band with its words, the conflict screen with its findings, the duplicates stated. All derived now; nothing stored.
  • PATCH /offices/leads/{lead_id}/status · scope offices:writeSet Lead StatusOF2: closing takes a REASON in words (a decline that teaches the desk nothing is wasted), and the first move to 'engaged' starts the response clock if nothing did.
  • POST /offices/meeting-records · scope offices:writeCreate Meeting Record
  • POST /offices/meeting-records/{record_id}/summarize · scope offices:writeSummarize MeetingAI drafts a summary + decision log from the uploaded notes/transcript only, same "drafts, never authoritative" pattern as every other AI feature in this project - a human reviews and can edit before it's treated as the real record.
  • GET /offices/mine · scope offices:readGet My Office Endpoint
  • POST /offices/mobile-kits · scope offices:writeRegister Mobile Kit
  • PATCH /offices/mobile-kits/{kit_id}/check · scope offices:writeCheck Mobile Kit
  • GET /offices/network · scope offices:readNetwork Desk
  • GET /offices/network/directory · scope offices:readNetwork DirectoryPractices a lead can be passed to: those that switched their page on (the same visibility the storefront honours), never the caller's own. Names, categories, regions - nothing more.
  • GET /offices/network/reading · scope offices:readNetwork Reading
  • POST /offices/network/{referral_id}/accept · scope offices:writeAccept Network ReferralThe receiving practice's hand: the lead is born on its own desk (source 'network'), once. The source lead's facts are read on the system cursor - the consent words recorded on the referral are exactly what authorises that.
  • POST /offices/network/{referral_id}/decline · scope offices:writeDecline Network Referral
  • POST /offices/payments/webhook · scope offices:writeOffices Stripe WebhookThe pillar's own verified door, Services' shape exactly: signature or refusal, captured events only, idempotent by the pending->paid guard plus the fee ledger's unique payment id.
  • POST /offices/payments/{payment_id}/pay · scope offices:writePay PaymentThe generic client door for a pending part (instalment, invoice) the client is named on.
  • POST /offices/projects · scope offices:writeCreate Project
  • GET /offices/projects/{project_id} · scope offices:readGet Project
  • GET /offices/projects/{project_id}/deliverables · scope offices:readList Deliverables
  • PATCH /offices/projects/{project_id}/due-date · scope offices:writeSet Project Due DateOF9 - the deadline as a date the agent can measure. NULL clears it (stated, never invented).
  • POST /offices/projects/{project_id}/expenses · scope offices:writeAdd Expense
  • POST /offices/projects/{project_id}/invoice · scope offices:writeMint InvoiceUninvoiced billable time x rate + billable expenses + a fixed fee -> ONE ar_invoice (bookkeeping's row, 'sent', due by the practice's terms) with the office_invoice link. Entries and expenses are stamped with the invoice so they can never be billed twice. Refused in words when there is nothing to bill.
  • GET /offices/projects/{project_id}/invoices · scope offices:readList Matter Invoices
  • PATCH /offices/projects/{project_id}/status · scope offices:writeSet Project Status
  • GET /offices/projects/{project_id}/tasks · scope offices:readList Tasks
  • POST /offices/projects/{project_id}/time · scope offices:writeAdd Time
  • GET /offices/projects/{project_id}/time · scope offices:readList Time
  • POST /offices/projects/{project_id}/time/{entry_id}/stop · scope offices:writeStop Time
  • POST /offices/retainers · scope offices:writeCreate RetainerA DRAFT tenant_plan (pillar 'offices') + the link naming the ONE client. Publishing passes recurring billing's own pay-to-open gate; subscribing is the named client's door.
  • GET /offices/retainers · scope offices:readList Retainers
  • POST /offices/retainers/{retainer_id}/publish · scope offices:writePublish Retainer
  • POST /offices/retainers/{retainer_id}/subscribe · scope offices:writeSubscribe RetainerThe NAMED client's door - recurring billing's checkout on the practice's own rail. Anyone else is refused; the plan never appears on the public storefront list.
  • GET /offices/room · scope offices:readMy RoomsEvery room the signed-in person holds, across practices - their own cursor, the tables' own client arms; nothing here is read on the practice's behalf.
  • POST /offices/room/{workspace_id}/appointments/{booking_id}/cancel · scope offices:writeCancel My AppointmentOF9 - the client cancels their own appointment from the room. A paid consultation is cancelled too, and the sentence is honest: nothing is refunded by a machine - the practice decides and refunds by hand on its own account.
  • POST /offices/room/{workspace_id}/appointments/{booking_id}/reschedule · scope offices:writeReschedule My AppointmentOF9 - a new time must be one the practice actually offers (the ONE slot arithmetic). An unpaid appointment goes back to 'requested' for the practice to confirm; a paid one stays confirmed - the money was the yes.
  • GET /offices/room/{workspace_id}/appointments/{booking_id}/slots · scope offices:readMy Appointment SlotsThe times the practice offers for this appointment's type on a day - so the room can reschedule onto a real slot, from the ONE slot arithmetic.
  • POST /offices/room/{workspace_id}/messages · scope offices:writePost MessageA human writes - the client of the room, or the practice's staff. The insert policy is the fence; the author_kind is decided here from who the caller is, never claimed.
  • GET /offices/room/{workspace_id}/messages · scope offices:readRoom Messages
  • POST /offices/room/{workspace_id}/refer · scope offices:writeRefer A PersonTHE REFERRAL DESK. The client's words about the person's consent are recorded as the fact they are; the practice gets ONE lead (source 'referral') through the one writer.
  • POST /offices/room/{workspace_id}/satisfaction · scope offices:writeRateAfter the work: one score per matter (accepted or closed), the client's own hand. This is the last link of the moat's measurement - lead -> meeting -> funded -> satisfied.
  • POST /offices/room/{workspace_id}/tasks/{task_id}/done · scope offices:writeFinish Owed TaskThe client finishes something they owed. Fenced in code (their room, a task owed by the client, on a matter of that room); the write rides the system cursor because the task table's update arm belongs to the practice.
  • POST /offices/status-updates · scope offices:writePost Status Update
  • POST /offices/tasks · scope offices:writeCreate Task
  • PATCH /offices/tasks/{task_id}/status · scope offices:writeSet Task Status
  • GET /offices/verticals · scope offices:readList Practice VerticalsThe catalog, one authoritative source - the web mirror is generated from the same file, and a guard fails when it drifts.
  • GET /offices/{office_id} · scope offices:readGet Office
  • GET /offices/{office_id}/agreement-templates · scope offices:readList Agreement Templates
  • GET /offices/{office_id}/agreements · scope offices:readList Agreements
  • GET /offices/{office_id}/bookings · scope offices:readList Bookings
  • PATCH /offices/{office_id}/category · scope offices:writeSet Practice CategoryThe picker: one of the verticals (the DB CHECK widened WITH this list in 521). The kit, the band reader and the storefront label all follow it.
  • GET /offices/{office_id}/client-workspaces · scope offices:readList Client Workspaces
  • POST /offices/{office_id}/client-workspaces · scope offices:writeCreate Client WorkspaceOF6 walk: the desk opens a room by the client's EMAIL (the hub's uuid field stays for callers that hold one). A client must already hold a Fitinty account - stated in words.
  • GET /offices/{office_id}/client-workspaces/mine · scope offices:readGet My Client Workspace
  • GET /offices/{office_id}/consultation-types · scope offices:readList Consultation Types
  • POST /offices/{office_id}/consultation-types · scope offices:writeCreate Consultation Type
  • PATCH /offices/{office_id}/consultation-types/{type_id} · scope offices:writeUpdate Consultation Type
  • GET /offices/{office_id}/consultations · scope offices:readList Consultations
  • GET /offices/{office_id}/dns-plan · scope offices:readDns Plan
  • POST /offices/{office_id}/dns-provision · scope offices:writeDns Provision
  • POST /offices/{office_id}/dns-verify · scope offices:writeDns Verify
  • GET /offices/{office_id}/documents · scope offices:readList Documents
  • POST /offices/{office_id}/documents · scope offices:writeUpload Document
  • GET /offices/{office_id}/domain-configuration · scope offices:readGet Domain Config
  • PATCH /offices/{office_id}/domain-configuration · scope offices:writeUpdate Domain Config
  • GET /offices/{office_id}/email-configuration · scope offices:readGet Email Config
  • PATCH /offices/{office_id}/email-configuration · scope offices:writeUpdate Email Config
  • POST /offices/{office_id}/email-verify · scope offices:writeEmail Verify
  • GET /offices/{office_id}/handoff-packages · scope offices:readList Handoff Packages
  • GET /offices/{office_id}/hours · scope offices:readGet Hours
  • PUT /offices/{office_id}/hours · scope offices:writeSet HoursReplaced wholesale - a week is one statement, not a diff a later reader has to replay. OF10 - with ?professional_user_id= it is THAT member's week (and makes them bookable); an empty list removes it (they leave the public picker).
  • GET /offices/{office_id}/intake-forms · scope offices:readList Intake Forms
  • GET /offices/{office_id}/intake-submissions · scope offices:readList Intake Submissions
  • GET /offices/{office_id}/kit · scope offices:readPractice Kit
  • POST /offices/{office_id}/kit/mint · scope offices:writeMint Practice KitMints the vertical's kit as DRAFTS: an intake form, consultation types (inactive), an engagement-letter template with the disclosure. Building is free; switching any door on is pay-to-open's business. Skips what already exists by title, so minting twice doubles nothing.
  • GET /offices/{office_id}/lobby · scope offices:readGet Lobby
  • PATCH /offices/{office_id}/lobby · scope offices:writeUpdate Lobby
  • GET /offices/{office_id}/meeting-records · scope offices:readList Meeting Records
  • GET /offices/{office_id}/members · scope offices:readList Office Members
  • POST /offices/{office_id}/members · scope offices:writeAdd Office Member
  • GET /offices/{office_id}/mobile-kits · scope offices:readList Mobile Kits
  • GET /offices/{office_id}/professionals · scope offices:readList ProfessionalsThe roster with who is BOOKABLE (has their own week) - the desk's picker.
  • GET /offices/{office_id}/profile · scope offices:readGet Office Profile
  • PATCH /offices/{office_id}/profile · scope offices:writeUpdate Office Profile
  • GET /offices/{office_id}/projects · scope offices:readList Projects
  • GET /offices/{office_id}/slots · scope offices:readDesk SlotsThe desk's view of open times - the same arithmetic the public door offers.
  • GET /offices/{office_id}/status-updates · scope offices:readList Status Updates
Bank - 54 doors - bank:read, bank:write

Accounts, ledgers and transactions - the books beneath the money.

  • POST /bank · scope bank:writeCreate Profile
  • GET /bank/currencies · scope bank:readList Currencies
  • POST /bank/disputes · scope bank:writeCreate Dispute
  • GET /bank/disputes/evidence/{evidence_id}/download · scope bank:readDownload Dispute Evidence
  • GET /bank/disputes/{case_id}/evidence · scope bank:readList Dispute Evidence
  • POST /bank/disputes/{case_id}/evidence · scope bank:writeUpload Dispute Evidence
  • PATCH /bank/disputes/{case_id}/status · scope bank:writeUpdate Dispute Status
  • POST /bank/fee-policies · scope bank:writeCreate Fee Policy
  • PATCH /bank/fee-policies/{policy_id}/approve · scope bank:writeApprove Fee Policy
  • PATCH /bank/identity-gates/{gate_id} · scope bank:writeUpdate Identity GateRefuses, and names the one door. A second place to say "verified" is a second answer, and two answers to a regulator's question is the defect CO4 was written to end.
  • GET /bank/integrations · scope bank:readList Integrations
  • POST /bank/ledger/journals/synthetic · scope bank:writeCreate Synthetic Journal
  • POST /bank/ledger/journals/{journal_id}/reverse · scope bank:writeReverse JournalPosted entries are immutable (spec section 11) - a reversal is a brand new journal whose entries swap debit/credit from the original, referencing the original via reverses_entry_id. The original journal/entries are never edited or deleted.
  • POST /bank/locations · scope bank:writeCreate Merchant Location
  • GET /bank/mine · scope bank:readGet My Profile Endpoint
  • GET /bank/payments/authorization · scope bank:readGet Authorization
  • POST /bank/payments/authorization · scope bank:writeRecord AuthorizationOwner-only. Records the Emperor's attestation that legal counsel has confirmed real-money go-live. This is the ONLY way the legal gate is ever set — no automated path creates it.
  • POST /bank/payments/authorization/revoke · scope bank:writeRevoke AuthorizationOwner-only. Revokes any active go-live authorization (real payments revert to not-authorized).
  • GET /bank/payments/readiness · scope bank:readReadiness
  • POST /bank/payouts · scope bank:writeCreate Payout
  • PATCH /bank/payouts/{payout_id}/advance · scope bank:writeAdvance PayoutAdvances a payout one real step through its state machine, enforcing the actual KYC/tax gate states along the way - no real payout instruction is ever sent (spec section 18: 'No real payout instruction'), this only tracks the mock lifecycle.
  • PATCH /bank/payouts/{payout_id}/status · scope bank:writeSet Payout Status
  • POST /bank/pos-profiles · scope bank:writeCreate Pos Profile
  • POST /bank/qr-payments/simulate · scope bank:writeSimulate Qr Payment
  • PATCH /bank/qr-payments/{qr_id}/status · scope bank:writeUpdate Qr Payment Status
  • POST /bank/reconciliation/run · scope bank:writeRun Reconciliation
  • POST /bank/settlements/calculate · scope bank:writeCalculate Settlement
  • POST /bank/transactions/simulate · scope bank:writeSimulate Transaction
  • PATCH /bank/transactions/{txn_id}/status · scope bank:writeUpdate Transaction Status
  • PATCH /bank/trust-scores/{snapshot_id}/appeal · scope bank:writeAppeal Trust Score
  • PATCH /bank/trust-scores/{snapshot_id}/review-appeal · scope bank:writeReview Trust Score Appeal
  • POST /bank/webhooks/simulate · scope bank:writeSimulate Webhook
  • GET /bank/{profile_id} · scope bank:readGet Profile
  • GET /bank/{profile_id}/disputes · scope bank:readList Disputes
  • GET /bank/{profile_id}/fee-policies · scope bank:readList Fee Policies
  • GET /bank/{profile_id}/identity-gates · scope bank:readList Identity GatesReads the ONE gate, for the organization this merchant belongs to.
  • GET /bank/{profile_id}/ledger/accounts · scope bank:readList Ledger Accounts
  • GET /bank/{profile_id}/ledger/journals · scope bank:readList Ledger Journals
  • GET /bank/{profile_id}/locations · scope bank:readList Merchant Locations
  • GET /bank/{profile_id}/members · scope bank:readList Members
  • POST /bank/{profile_id}/members · scope bank:writeAdd Member
  • GET /bank/{profile_id}/payouts · scope bank:readList Payouts
  • GET /bank/{profile_id}/pos-profiles · scope bank:readList Pos Profiles
  • GET /bank/{profile_id}/purse · scope bank:readGet Purse
  • GET /bank/{profile_id}/qr-payments · scope bank:readList Qr Payments
  • GET /bank/{profile_id}/reconciliation · scope bank:readList Reconciliation Runs
  • GET /bank/{profile_id}/settlements · scope bank:readList Settlements
  • GET /bank/{profile_id}/transactions · scope bank:readList Transactions
  • GET /bank/{profile_id}/trust-policies · scope bank:readList Trust Policies
  • GET /bank/{profile_id}/trust-scores · scope bank:readList Trust Scores
  • POST /bank/{profile_id}/trust-scores/compute · scope bank:writeCompute Trust Score
  • GET /bank/{profile_id}/wealth · scope bank:readList Wealth Snapshots
  • POST /bank/{profile_id}/wealth/snapshot · scope bank:writeGenerate Wealth Snapshot
  • GET /bank/{profile_id}/webhook-events · scope bank:readList Webhook Events
Treasury - 107 doors - treasury:read, treasury:write

Reserves, payroll, settlement and reimbursement.

  • POST /treasury · scope treasury:writeCreate Profile
  • POST /treasury/annual-exports · scope treasury:writeCreate Annual Export
  • PATCH /treasury/annual-exports/{pkg_id}/status · scope treasury:writeSet Annual Export Status
  • POST /treasury/benefit-pools · scope treasury:writeCreate Benefit Pool
  • POST /treasury/benefit-pools/entries · scope treasury:writeCreate Benefit Pool Entry
  • POST /treasury/contractor-payouts/simulate · scope treasury:writeSimulate Contractor Payout
  • PATCH /treasury/contractor-payouts/{payout_id}/status · scope treasury:writeSet Contractor Payout Status
  • POST /treasury/contractor-profiles · scope treasury:writeCreate Contractor Profile
  • GET /treasury/contractor-profiles/{profile_id}/1099/{year} · scope treasury:readSimulated 1099A SIMULATED 1099-shaped summary - `contractor_1099_figures`, the one arithmetic.
  • POST /treasury/fee-policies · scope treasury:writeCreate Fee Policy
  • PATCH /treasury/fee-policies/{policy_id}/approve · scope treasury:writeApprove Fee Policy
  • GET /treasury/integrations · scope treasury:readList Integrations
  • POST /treasury/ledger/journals/synthetic · scope treasury:writeCreate Synthetic Journal
  • POST /treasury/ledger/journals/{journal_id}/reverse · scope treasury:writeReverse JournalPosted entries are immutable (spec section 11) - a reversal is a brand new journal whose entries swap debit/credit from the original, referencing the original via reverses_entry_id. The original journal/entries are never edited or deleted.
  • POST /treasury/member-equity · scope treasury:writeCreate Member Equity Entry
  • POST /treasury/member-resolutions · scope treasury:writeCreate Member Resolution
  • PATCH /treasury/member-resolutions/{res_id}/adopt · scope treasury:writeAdopt Member Resolution
  • GET /treasury/mine · scope treasury:readGet My Profile Endpoint
  • GET /treasury/my-pay · scope treasury:readMy Pay
  • POST /treasury/owner-draws/propose · scope treasury:writePropose Owner DrawReal reserve checks, not decorative text: a draw is proposed with tax_reserve_check and maintenance_reserve_check computed live from the tenant's own real approved reserve-policy balances against the requested amount - genuinely blocking, not just informational.
  • PATCH /treasury/owner-draws/{draw_id}/status · scope treasury:writeSet Owner Draw StatusApproval is real record-keeping only - spec section 15/27: no real transfer ever occurs.
  • POST /treasury/payouts · scope treasury:writeCreate Payout
  • PATCH /treasury/payouts/{payout_id}/advance · scope treasury:writeAdvance PayoutAdvances a payout one real step through its state machine, enforcing the tenant's actual identity and tax gates through the platform's ONE definition of them (CO4, 563). This pillar read Compliance's system of record from the start rather than keeping a Treasury copy; Bank kept a copy, the two disagreed on live data, and 563 ended that. No real payout instruction is ever sent.
  • PATCH /treasury/payouts/{payout_id}/status · scope treasury:writeSet Payout Status
  • POST /treasury/payroll-items · scope treasury:writeCreate Payroll ItemReal, hand-verifiable net-pay math: base pay is either hours x rate (hourly workers) or the salary placeholder directly, gross = base + bonus, net = gross - tax - benefit - genuinely computed, never a fabricated number.
  • POST /treasury/payroll-items/{item_id}/pull-benefits · scope treasury:writePull Benefit DeductionsBenefits flow into net FROM THE ELECTIONS THAT EXIST: the linked employee's elected plans with a numeric cost are summed into the deduction; a plan whose cost is only prose is NAMED as not deducted - never guessed from text.
  • GET /treasury/payroll-items/{item_id}/stub · scope treasury:readPay StubThe pay stub, in words and numbers, stamped SIMULATED. Every figure is the row it came from - gross, each named tax line, the benefit deduction, net.
  • POST /treasury/payroll-items/{item_id}/tax-lines · scope treasury:writeSet Tax LinesTHE ONE WRITER for an item's tax shape. Replaces the lines wholesale, keeps the item's tax_placeholder equal to their sum, and recomputes net - so every old reader (the run totals, the synthetic journal) stays honest without knowing about lines. An unknown kind is refused (422): sources are data, readers are code, and a kind with no reader would be a silent zero.
  • POST /treasury/payroll-profiles · scope treasury:writeCreate Payroll Profile
  • POST /treasury/payroll-profiles/{payroll_profile_id}/rail-sync · scope treasury:writePost Sync Worker
  • PATCH /treasury/payroll-profiles/{profile_id}/link-employee · scope treasury:writeLink Profile EmployeeThe bridge: the simulated profile names the REAL employee it mirrors. The employee must belong to the same organization as the treasury profile - a stub attached to a stranger would be a fabrication.
  • GET /treasury/payroll-profiles/{profile_id}/w2/{year} · scope treasury:readSimulated W2A SIMULATED W-2-shaped summary of the year - `w2_figures`, the one arithmetic.
  • POST /treasury/payroll-runs · scope treasury:writeCreate Payroll Run
  • PATCH /treasury/payroll-runs/{run_id}/status · scope treasury:writeSet Payroll Run StatusAdvancing to posted_mock posts a real, hand-verifiable double-entry journal: debit Payroll Expense for the full gross total, credit Payroll Liabilities for the same amount, then a second entry moving the withheld-tax portion from Payroll Liabilities into Tax Reserve - no real employee payment ever occurs (spec section 12).
  • POST /treasury/payroll-runs/{run_id}/transmit · scope treasury:writePost Transmit
  • POST /treasury/protection-pools · scope treasury:writeCreate Protection Pool
  • POST /treasury/protection-pools/entries · scope treasury:writeCreate Protection Pool Entry
  • POST /treasury/reconciliation/run · scope treasury:writeRun Reconciliation
  • POST /treasury/reimbursements · scope treasury:writeCreate Reimbursement
  • GET /treasury/reimbursements/{req_id}/receipt · scope treasury:readDownload Receipt
  • PATCH /treasury/reimbursements/{req_id}/status · scope treasury:writeSet Reimbursement StatusMarking paid_mock posts a real ledger journal (debit Reimbursements expense, credit Cash) - still no real money movement, just the honest bookkeeping record of the mock payment (spec section 14).
  • POST /treasury/reports/monthly · scope treasury:writeCreate Financial Reportgross_profit and net_income are always genuinely computed server-side from the caller's own revenue/cogs/expense inputs, never accepted as client-supplied totals.
  • PATCH /treasury/reports/{report_id}/finalize · scope treasury:writeFinalize Financial Report
  • POST /treasury/reserve-policies · scope treasury:writeCreate Reserve Policy
  • PATCH /treasury/reserve-policies/{policy_id}/approve · scope treasury:writeApprove Reserve Policy
  • POST /treasury/reserves/simulate · scope treasury:writeSimulate Reserve Allocation
  • POST /treasury/settlements/calculate · scope treasury:writeCalculate Settlement
  • POST /treasury/tax-reserves · scope treasury:writeCreate Tax Reserve
  • POST /treasury/webhooks/simulate · scope treasury:writeSimulate Webhook
  • GET /treasury/{profile_id} · scope treasury:readGet Profile
  • GET /treasury/{profile_id}/annual-exports · scope treasury:readList Annual Exports
  • GET /treasury/{profile_id}/assets · scope treasury:readGet Assets
  • POST /treasury/{profile_id}/assets · scope treasury:writePost Asset
  • POST /treasury/{profile_id}/assets/{asset_id}/dispose · scope treasury:writePost Dispose
  • GET /treasury/{profile_id}/benefit-pools · scope treasury:readList Benefit Pools
  • GET /treasury/{profile_id}/close/{period} · scope treasury:readRead Close
  • POST /treasury/{profile_id}/close/{period} · scope treasury:writeClose Period
  • POST /treasury/{profile_id}/close/{period}/reopen · scope treasury:writeReopen Period
  • GET /treasury/{profile_id}/closes · scope treasury:readList Closes
  • GET /treasury/{profile_id}/contractor-payouts · scope treasury:readList Contractor Payouts
  • GET /treasury/{profile_id}/contractor-profiles · scope treasury:readList Contractor Profiles
  • GET /treasury/{profile_id}/fee-policies · scope treasury:readList Fee Policies
  • GET /treasury/{profile_id}/feed · scope treasury:readGet FeedRead-through on open: reads the provider, posts what is new, then reads the books.
  • POST /treasury/{profile_id}/feed/sync · scope treasury:writePost Feed
  • GET /treasury/{profile_id}/inflows · scope treasury:readGet InflowsRead-through on open: brings the books up to date, then reads them. The age is stated.
  • POST /treasury/{profile_id}/inflows/sync · scope treasury:writePost Inflows
  • POST /treasury/{profile_id}/investments · scope treasury:writePost Investment
  • POST /treasury/{profile_id}/investments/{investment_id}/mark · scope treasury:writePost Mark
  • POST /treasury/{profile_id}/investments/{investment_id}/sell · scope treasury:writePost Sell
  • GET /treasury/{profile_id}/ledger/accounts · scope treasury:readList Ledger Accounts
  • GET /treasury/{profile_id}/ledger/journals · scope treasury:readList Ledger Journals
  • GET /treasury/{profile_id}/member-equity · scope treasury:readList Member Equity
  • GET /treasury/{profile_id}/member-equity/summary · scope treasury:readMember Equity Summary
  • GET /treasury/{profile_id}/member-resolutions · scope treasury:readList Member Resolutions
  • GET /treasury/{profile_id}/members · scope treasury:readList Members
  • POST /treasury/{profile_id}/members · scope treasury:writeAdd Member
  • GET /treasury/{profile_id}/nightly · scope treasury:readTenant NightlyA member reads when the timer last opened THEIR treasury and what it posted - nothing else.
  • GET /treasury/{profile_id}/owner-draws · scope treasury:readList Owner Draws
  • GET /treasury/{profile_id}/payouts · scope treasury:readList Payouts
  • GET /treasury/{profile_id}/payroll-profiles · scope treasury:readList Payroll Profiles
  • GET /treasury/{profile_id}/payroll-rail · scope treasury:readGet Rail
  • POST /treasury/{profile_id}/payroll-rail/employer · scope treasury:writePost Employer
  • GET /treasury/{profile_id}/payroll-runs · scope treasury:readList Payroll Runs
  • GET /treasury/{profile_id}/protection-pools · scope treasury:readList Protection Pools
  • GET /treasury/{profile_id}/reconciliation · scope treasury:readList Reconciliation Runs
  • GET /treasury/{profile_id}/reimbursements · scope treasury:readList Reimbursements
  • GET /treasury/{profile_id}/reports · scope treasury:readGet Reports
  • POST /treasury/{profile_id}/reports · scope treasury:writePost Report
  • GET /treasury/{profile_id}/reports/monthly · scope treasury:readList Financial Reports
  • GET /treasury/{profile_id}/reports/{report_id} · scope treasury:readGet One
  • GET /treasury/{profile_id}/reports/{report_id}/export/{fmt} · scope treasury:readGet ExportR4 - the report as a file: csv, xlsx (native charts), pdf (the droplet's OnlyOffice), json.
  • POST /treasury/{profile_id}/reports/{report_id}/finalize · scope treasury:writePost Finalize
  • GET /treasury/{profile_id}/reports/{report_id}/html · scope treasury:readGet Html
  • PUT /treasury/{profile_id}/reports/{report_id}/narrative · scope treasury:writePut Words
  • POST /treasury/{profile_id}/reports/{report_id}/narrative/approve · scope treasury:writePost Words Approve
  • POST /treasury/{profile_id}/reports/{report_id}/narrative/draft · scope treasury:writePost Words Draft
  • POST /treasury/{profile_id}/reports/{report_id}/narrative/remove · scope treasury:writePost Words Remove
  • POST /treasury/{profile_id}/reports/{report_id}/withdraw · scope treasury:writePost Withdraw
  • GET /treasury/{profile_id}/reserve-accounts · scope treasury:readList Reserve Accounts
  • GET /treasury/{profile_id}/reserve-policies · scope treasury:readList Reserve Policies
  • GET /treasury/{profile_id}/settlements · scope treasury:readList Settlements
  • GET /treasury/{profile_id}/statements · scope treasury:readStatements
  • GET /treasury/{profile_id}/statements/export/{fiscal_year} · scope treasury:readExport Fiscal YearThe CPA package: the four statements, the trial balance, the aging and the full ledger of one fiscal year, in one document, with the method beside every figure.
  • GET /treasury/{profile_id}/statements/ledger · scope treasury:readLedger Detail
  • PUT /treasury/{profile_id}/statements/settings · scope treasury:writeSet Fiscal
  • GET /treasury/{profile_id}/tax-reserves · scope treasury:readList Tax Reserves
  • GET /treasury/{profile_id}/webhook-events · scope treasury:readList Webhook Events
Tax - 91 doors - tax:read, tax:write

Filings, forms, reserves and the identity behind them.

  • POST /tax · scope tax:writeCreate Profile
  • POST /tax/annual-packages · scope tax:writeCreate Annual Packagepnl_net_income is always genuinely computed server-side, never accepted as a client- supplied total. receipt_index_count and the JSON-TX manifest's included_reports are both counted live from the tenant's own real data for this tax_year.
  • PATCH /tax/annual-packages/{package_id}/status · scope tax:writeSet Annual Package Status
  • POST /tax/calendar · scope tax:writeCreate Calendar Event
  • PATCH /tax/calendar/{event_id}/status · scope tax:writeSet Calendar Event Status
  • POST /tax/forms/prepare · scope tax:writePrepare Form
  • PATCH /tax/forms/{form_id}/review · scope tax:writeReview Form
  • POST /tax/identity-requirements · scope tax:writeCreate Identity Requirement
  • PATCH /tax/identity-requirements/{req_id}/status · scope tax:writeSet Identity Requirement Status
  • GET /tax/integrations · scope tax:readList Integrations
  • GET /tax/mine · scope tax:readGet My Profile Endpoint
  • POST /tax/receipts · scope tax:writeCreate Receipt
  • GET /tax/receipts/{receipt_id}/file · scope tax:readDownload Receipt File
  • PATCH /tax/receipts/{receipt_id}/review · scope tax:writeReview Receipt
  • POST /tax/reconciliation/run · scope tax:writeRun ReconciliationTX11 - real rows against real rows; the lines stored as they stood (tax_reconciliation.run).
  • POST /tax/reserve-policies · scope tax:writeCreate Reserve Policy
  • PATCH /tax/reserve-policies/{policy_id}/approve · scope tax:writeApprove Reserve Policy
  • POST /tax/reserves/simulate · scope tax:writeSimulate Reserve
  • POST /tax/sales-tax-policies · scope tax:writeCreate Sales Tax Policy
  • PATCH /tax/sales-tax-policies/{policy_id}/settings · scope tax:writeSet Sales Tax SettingsTX9 - the jurisdiction code a policy's captures match (US-TX, GB, NG) and the dial that asks the payment provider's own tax calculation at checkout, on the TENANT'S account (tenant_payments.checkout_tax). This module names no rail: the Stop 15 guard forbids it, and the dial is only a fact on the row.
  • PATCH /tax/sales-tax-policies/{policy_id}/toggle-collection · scope tax:writeToggle Sales Tax Collection
  • POST /tax/state-nexus · scope tax:writeCreate Nexus Profile
  • PATCH /tax/state-nexus/{nexus_id}/review · scope tax:writeReview Nexus Profile
  • POST /tax/tips-overtime · scope tax:writeCreate Tip Overtime
  • PATCH /tax/tips-overtime/{record_id}/review · scope tax:writeReview Tip Overtime
  • POST /tax/transactions · scope tax:writeCreate Tax Transaction
  • POST /tax/vault · scope tax:writeUpload Vault Item
  • GET /tax/vault/{item_id}/file · scope tax:readDownload Vault Item
  • PATCH /tax/vault/{item_id}/review · scope tax:writeReview Vault Item
  • PATCH /tax/verification/simulate · scope tax:writeSimulate Verification
  • POST /tax/withholding-policies · scope tax:writeCreate Withholding Policy
  • PATCH /tax/withholding-policies/{policy_id}/approve · scope tax:writeApprove Withholding Policy
  • POST /tax/withholding/simulate · scope tax:writeSimulate WithholdingReal payout-tax gate simulation (spec section 14) - the outcome is genuinely computed from the tenant's own real tax_verification_state and whether any tax_identity_requirement has actually been approved, not a decorative status field: 1. No approved W-9/W-8 on file -> 100% hold simulated (spec: "If W-9/W-8 missing, simulate 100% hold where configured"). 2. Verification is verified_placeholder …
  • GET /tax/{profile_id} · scope tax:readGet Profile
  • GET /tax/{profile_id}/annual-packages · scope tax:readList Annual Packages
  • PATCH /tax/{profile_id}/basis · scope tax:writePatch BasisTX6 - the method the return follows. The books stay accrual; the tax desk reads this.
  • GET /tax/{profile_id}/calendar · scope tax:readList Calendar Events
  • PATCH /tax/{profile_id}/entity-type · scope tax:writePatch Entity TypeTX7 - the dial that chooses the estimate's shape. NULL clears it (estimates stop, in words).
  • PATCH /tax/{profile_id}/estimate-rate · scope tax:writePatch Rate
  • POST /tax/{profile_id}/estimates/{estimate_id}/record-payment · scope tax:writePost Payment
  • GET /tax/{profile_id}/filings · scope tax:readGet Filings
  • POST /tax/{profile_id}/filings/prepare · scope tax:writePost Prepare
  • POST /tax/{profile_id}/filings/{filing_id}/approve · scope tax:writePost Approve
  • POST /tax/{profile_id}/filings/{filing_id}/transmit · scope tax:writePost Transmit
  • POST /tax/{profile_id}/filings/{filing_id}/withdraw · scope tax:writePost Withdraw
  • GET /tax/{profile_id}/form-copies · scope tax:readGet Desk
  • PATCH /tax/{profile_id}/form-copies/{copy_id}/revoke · scope tax:writePatch Revoke
  • GET /tax/{profile_id}/forms · scope tax:readList Form Preparations
  • GET /tax/{profile_id}/forms/desk · scope tax:readGet Desk
  • POST /tax/{profile_id}/forms/prepare-year · scope tax:writePost Prepare Year
  • POST /tax/{profile_id}/forms/{form_id}/copy · scope tax:writePost Copy
  • POST /tax/{profile_id}/forms/{form_id}/correct · scope tax:writePost Correct
  • GET /tax/{profile_id}/forms/{form_id}/export/{fmt} · scope tax:readGet Form Export
  • GET /tax/{profile_id}/forms/{form_id}/html · scope tax:readGet Form Html
  • POST /tax/{profile_id}/forms/{form_id}/request-identity · scope tax:writePost Request Identity For Form
  • PATCH /tax/{profile_id}/forms/{form_id}/void · scope tax:writePatch Void
  • GET /tax/{profile_id}/identity-requirements · scope tax:readList Identity Requirements
  • POST /tax/{profile_id}/identity-requirements/{req_id}/request · scope tax:writePost Request Identity
  • POST /tax/{profile_id}/identity-requirements/{req_id}/tin-attested · scope tax:writePost Attest
  • GET /tax/{profile_id}/inputs · scope tax:readGet InputsRead-through on open: captures become tax transactions, then the year is read from the books.
  • POST /tax/{profile_id}/inputs/sync · scope tax:writePost Sync
  • GET /tax/{profile_id}/journals · scope tax:readGet DeskRead-through on open: what is due posts, then the desk is read.
  • POST /tax/{profile_id}/journals/run · scope tax:writePost Run
  • GET /tax/{profile_id}/jurisdictions · scope tax:readGet Jurisdictions
  • POST /tax/{profile_id}/jurisdictions · scope tax:writePost Jurisdiction
  • PATCH /tax/{profile_id}/jurisdictions/{reg_id} · scope tax:writePatch Jurisdiction
  • GET /tax/{profile_id}/lines · scope tax:readGet Lines
  • PUT /tax/{profile_id}/lines/{account_id} · scope tax:writePut LineMap an account to a line of the desk's form by hand; NULL returns it to the default.
  • GET /tax/{profile_id}/members · scope tax:readList Members
  • POST /tax/{profile_id}/members · scope tax:writeAdd Member
  • GET /tax/{profile_id}/nexus · scope tax:readList Nexus Profiles
  • GET /tax/{profile_id}/packs · scope tax:readGet Packs
  • POST /tax/{profile_id}/packs · scope tax:writePost Pack
  • GET /tax/{profile_id}/receipts · scope tax:readList Receipts
  • GET /tax/{profile_id}/receipts-desk · scope tax:readGet Desk
  • PATCH /tax/{profile_id}/receipts/{receipt_id}/link · scope tax:writePatch Link
  • POST /tax/{profile_id}/receipts/{receipt_id}/post · scope tax:writePost Receipt
  • PATCH /tax/{profile_id}/receipts/{receipt_id}/unlink · scope tax:writePatch Unlink
  • GET /tax/{profile_id}/reconciliation · scope tax:readList Reconciliation Runs
  • GET /tax/{profile_id}/reconciliation/preview · scope tax:readPreviewThe read without a stored run - what a run would say now.
  • PATCH /tax/{profile_id}/reconciliation/{run_id}/review · scope tax:writePatch Review
  • GET /tax/{profile_id}/reserve-policies · scope tax:readList Reserve Policies
  • GET /tax/{profile_id}/reserve-simulations · scope tax:readList Reserve Simulations
  • GET /tax/{profile_id}/sales-tax-policies · scope tax:readList Sales Tax Policies
  • POST /tax/{profile_id}/sales-tax/{policy_id}/record-remittance · scope tax:writePost Remit
  • GET /tax/{profile_id}/tips-overtime · scope tax:readList Tips Overtime
  • GET /tax/{profile_id}/transactions · scope tax:readList Tax Transactions
  • GET /tax/{profile_id}/vault · scope tax:readList Vault Items
  • GET /tax/{profile_id}/verification · scope tax:readGet Verification State
  • GET /tax/{profile_id}/withholding-policies · scope tax:readList Withholding Policies
  • GET /tax/{profile_id}/withholding-simulations · scope tax:readList Withholding Simulations
Compliance - 88 doors - compliance:read, compliance:write

Policy, evidence, incidents and review - what keeps the rest defensible.

  • POST /compliance · scope compliance:writeCreate Profile
  • POST /compliance/approvals · scope compliance:writeCreate Approval
  • PATCH /compliance/approvals/{approval_id}/revoke · scope compliance:writeRevoke Approval
  • GET /compliance/authority/register · scope compliance:readGet AuthoritiesThe Emperor's alone: these rows carry counsel references and attestations for the whole platform, and every one of the tables behind them is owner-only by its own policy.
  • POST /compliance/brand-rules · scope compliance:writeCreate Brand Rule
  • POST /compliance/cross-pillar-requirements · scope compliance:writeCreate Cross Pillar Requirement
  • PATCH /compliance/cross-pillar-requirements/{req_id}/status · scope compliance:writeUpdate Cross Pillar Requirement Status
  • POST /compliance/data-classifications · scope compliance:writeCreate Data Classification
  • POST /compliance/data-subject-requests · scope compliance:writeCreate Data Subject Request
  • PATCH /compliance/data-subject-requests/{req_id}/status · scope compliance:writeUpdate Data Subject Request Status
  • POST /compliance/delta-reports · scope compliance:writeCreate Delta Report
  • PATCH /compliance/delta-reports/{report_id}/close · scope compliance:writeClose Delta Report
  • POST /compliance/disputes · scope compliance:writeCreate Dispute
  • GET /compliance/disputes/{case_id}/evidence · scope compliance:readList Dispute Evidence
  • POST /compliance/disputes/{case_id}/evidence · scope compliance:writeUpload Dispute Evidence
  • PATCH /compliance/disputes/{case_id}/status · scope compliance:writeUpdate Dispute Status
  • POST /compliance/evidence · scope compliance:writeUpload Evidence
  • PATCH /compliance/evidence-links/{link_id}/detach · scope compliance:writePatch Detach
  • GET /compliance/evidence/{ev_id}/download · scope compliance:readDownload Evidence
  • PATCH /compliance/evidence/{ev_id}/status · scope compliance:writeUpdate Evidence Status
  • POST /compliance/evidence/{evidence_id}/links · scope compliance:writePost Link
  • GET /compliance/evidence/{evidence_id}/links · scope compliance:readGet What It Evidences
  • POST /compliance/exceptions · scope compliance:writeCreate Exception
  • PATCH /compliance/exceptions/{exc_id}/status · scope compliance:writeUpdate Exception Status
  • POST /compliance/gate-check · scope compliance:writeGate Check
  • POST /compliance/governance-records · scope compliance:writeCreate Governance Record
  • PATCH /compliance/governance-records/{rec_id}/adopt · scope compliance:writeAdopt Governance Record
  • PATCH /compliance/identity-gates/{gate_id} · scope compliance:writeUpdate Identity Gate
  • POST /compliance/incidents · scope compliance:writeCreate Incident
  • POST /compliance/incidents/{inc_id}/ai-draft-summary · scope compliance:writeAi Draft Incident SummaryAI drafts a plain-language incident summary from the recorded title/description - advisory only, never authoritative, matching this project's established pattern. Never triggers any automatic investigation, eviction, suspension, or disclosure (spec section 23).
  • PATCH /compliance/incidents/{inc_id}/status · scope compliance:writeUpdate Incident Status
  • GET /compliance/integrations · scope compliance:readList Integrations
  • GET /compliance/mine · scope compliance:readGet My Profile Endpoint
  • POST /compliance/policies · scope compliance:writeCreate Policy
  • POST /compliance/policies/{policy_id}/acknowledge · scope compliance:writePost Acknowledge
  • GET /compliance/policies/{policy_id}/acknowledgements · scope compliance:readGet Standing
  • POST /compliance/policies/{policy_id}/acknowledgements/record · scope compliance:writePost Record For
  • PATCH /compliance/policies/{policy_id}/status · scope compliance:writeUpdate Policy Status
  • GET /compliance/policies/{policy_id}/versions · scope compliance:readList Policy Versions
  • POST /compliance/provenance-manifests · scope compliance:writeIngest Provenance Manifest
  • POST /compliance/provenance-manifests/{manifest_id}/validate · scope compliance:writeValidate Provenance ManifestReal structural validation - confirms the manifest actually has an asset_hash on file (i.e., a real asset was ingested), never a cryptographic C2PA signature check (spec section 18: production signing needs a certificate policy/key management/production approval, none of which exist in Core).
  • GET /compliance/provenance-manifests/{manifest_id}/verifications · scope compliance:readList Provenance Verifications
  • POST /compliance/red-flags · scope compliance:writeCreate Red Flag
  • PATCH /compliance/red-flags/{rf_id}/status · scope compliance:writeUpdate Red Flag Status
  • POST /compliance/reimbursements · scope compliance:writeCreate Reimbursement
  • POST /compliance/reviews · scope compliance:writeCreate Review
  • GET /compliance/reviews/{case_id}/approvals · scope compliance:readList Approvals
  • POST /compliance/risks · scope compliance:writeCreate Risk
  • PATCH /compliance/risks/{risk_id} · scope compliance:writeUpdate Risk
  • GET /compliance/sanctions/board · scope compliance:readBoard Door
  • GET /compliance/sanctions/hits · scope compliance:readHits Door
  • POST /compliance/sanctions/hits/{hit_id}/close · scope compliance:writeClose HitA false positive - or a true one a person has dealt with outside - is CLOSED WITH A REASON. Who may: the platform's owner, or a member of the business the party belongs to (the wall says who can read the hit; that is who may answer it). The write goes on the platform's cursor because the hit table takes no tenant write - the fact is checked on the cursor that may check it, the row written on the o…
  • POST /compliance/sanctions/lists/register · scope compliance:writeRegister Lists DoorThe Emperor registers the shelf's four lists on the scout registry - kind `sanctions`, dial 0, idempotent. Nothing is fetched by this door.
  • POST /compliance/sanctions/rescreen · scope compliance:writeRescreen DoorHis hand: every party against the lists as they stand. Opens cases; holds nothing.
  • GET /compliance/{profile_id} · scope compliance:readGet Profile
  • GET /compliance/{profile_id}/brand-rules · scope compliance:readList Brand Rules
  • GET /compliance/{profile_id}/consents · scope compliance:readGet Consents
  • GET /compliance/{profile_id}/cross-pillar-requirements · scope compliance:readList Cross Pillar Requirements
  • GET /compliance/{profile_id}/dashboard · scope compliance:readGet Dashboard
  • GET /compliance/{profile_id}/data-classifications · scope compliance:readList Data Classifications
  • GET /compliance/{profile_id}/data-subject-requests · scope compliance:readList Data Subject Requests
  • GET /compliance/{profile_id}/delta-reports · scope compliance:readList Delta Reports
  • GET /compliance/{profile_id}/disputes · scope compliance:readList Disputes
  • GET /compliance/{profile_id}/evidence · scope compliance:readList Evidence
  • GET /compliance/{profile_id}/evidence-for/{kind}/{target_id} · scope compliance:readGet Evidence For
  • GET /compliance/{profile_id}/evidence-sweep · scope compliance:readGet Sweep
  • GET /compliance/{profile_id}/evidence-unlinked · scope compliance:readGet Unlinked
  • GET /compliance/{profile_id}/exceptions · scope compliance:readList Exceptions
  • GET /compliance/{profile_id}/gate-strikes · scope compliance:readList Gate Strikes
  • GET /compliance/{profile_id}/governance-records · scope compliance:readList Governance Records
  • GET /compliance/{profile_id}/identity-gates · scope compliance:readList Identity Gates
  • GET /compliance/{profile_id}/incidents · scope compliance:readList Incidents
  • GET /compliance/{profile_id}/members · scope compliance:readList Members
  • POST /compliance/{profile_id}/members · scope compliance:writeAdd Member
  • GET /compliance/{profile_id}/obligations · scope compliance:readGet Register
  • PATCH /compliance/{profile_id}/obligations/{key} · scope compliance:writePatch Obligation
  • GET /compliance/{profile_id}/packs · scope compliance:readGet Packs
  • POST /compliance/{profile_id}/packs · scope compliance:writePost Pack
  • GET /compliance/{profile_id}/policies · scope compliance:readList Policies
  • GET /compliance/{profile_id}/privacy · scope compliance:readGet Privacy Preference
  • PATCH /compliance/{profile_id}/privacy · scope compliance:writeUpdate Privacy Preference
  • GET /compliance/{profile_id}/provenance-manifests · scope compliance:readList Provenance Manifests
  • GET /compliance/{profile_id}/red-flags · scope compliance:readList Red Flags
  • GET /compliance/{profile_id}/reimbursements · scope compliance:readList Reimbursements
  • GET /compliance/{profile_id}/reviews · scope compliance:readList Reviews
  • GET /compliance/{profile_id}/risks · scope compliance:readList Risks
  • GET /compliance/{profile_id}/sanctions · scope compliance:readTenant DoorThe business's own open cases, in words. The desk's row answers who the business is; the hit rows answer on the member arm.
  • GET /compliance/{profile_id}/trail · scope compliance:readGet Trail
Investors - 227 doors - investors:read, investors:write

Research, strategy and simulated execution. No live broker connection.

  • GET /credit-repair/compliance/authorization · scope investors:readGet Authorization
  • POST /credit-repair/compliance/authorization · scope investors:writeRecord AuthorizationOwner-only. Records the Emperor's attestation that real legal counsel has reviewed a real CROA-compliant contract, a real pre-signup disclosure exists, and billing genuinely never charges up front. This is the ONLY way any of the three gates is ever set — no automated path creates it, and setting it does not itself build or enable any dispute-filing feature.
  • POST /credit-repair/compliance/authorization/revoke · scope investors:writeRevoke AuthorizationOwner-only. Revokes any active authorization (real automated credit-repair activity, if it ever existed, would revert to not-authorized).
  • GET /credit-repair/compliance/readiness · scope investors:readReadiness
  • POST /investors · scope investors:writeCreate Profile
  • GET /investors/alerting/alerts · scope investors:readList Open
  • POST /investors/alerting/alerts/{alert_id}/acknowledge · scope investors:writeAcknowledgeAcknowledge an alert. It stays quiet while the condition persists - and returns if it comes back after resolving, because the acknowledgement was about a situation that has since ended.
  • GET /investors/alerting/badge · scope investors:readBadgeOne small number for a navigation item, plus the one thing that number cannot say. WHAT THE BADGE COUNTS IS THE DESIGN. Not "alerts" - alerts include the ones somebody has already acknowledged and is dealing with, and a badge that keeps counting those is a permanent red dot, which is wallpaper within a week. That is INV19's fatigue failure moved up into the interface, and it would undo the restra…
  • GET /investors/alerting/channels · scope investors:readChannelsWhich channels exist and which of them are allowed to reach a person.
  • POST /investors/alerting/channels/{channel}/signoff · scope investors:writeSign Off ChannelRecord a written sign-off for a channel that reaches real people. The Emperor's alone. WHAT THIS DOES NOT DO: turn the channel on (`enabled` is a separate switch), decide whether the sign-off is sufficient, or shorten what the gate asks for. A channel with a recorded sign-off and `enabled` false still refuses, and says so in the answer.
  • POST /investors/alerting/channels/{channel}/signoff/revoke · scope investors:writeRevoke Channel SignoffWithdraw it. Immediate, and the withdrawal goes to the audit trail - which is where the authority register reads a lock's history from, because the row itself keeps no record of ever having been open.
  • POST /investors/alpha-audit · scope investors:writeAppend Alpha Audit Entry
  • POST /investors/assignment/profiles/{profile_id}/assign · scope investors:writeAssign EarlyEarly assignment: a short position taken from the account before expiry. There is no approval parameter and there is not going to be one. American options are assignable at any moment, the most common cause is a dividend the day before it goes ex, and the account gets no say - so the endpoint that models it gets no say either.
  • POST /investors/assignment/profiles/{profile_id}/exercise · scope investors:writeExerciseExercise a long option early. The one settlement on this platform that IS a decision.
  • GET /investors/assignment/profiles/{profile_id}/exposure · scope investors:readRead ExposureWhat could be done to this account today, without anybody asking it first.
  • POST /investors/assignment/profiles/{profile_id}/settle-expiry · scope investors:writeRun Expiry
  • GET /investors/assignment/profiles/{profile_id}/settlements · scope investors:readList Settlements
  • POST /investors/backtests · scope investors:writeCreate Backtest
  • GET /investors/backtests/{run_id}/report · scope investors:readBacktest ReportThe full evidence report: the verdict, every check, and every fold.
  • POST /investors/breaker-trips/{trip_id}/reset · scope investors:writeReset TripClear a trip. Needs a named person and a real reason - both enforced in the database. The strategy is NOT un-halted by this. Clearing the breaker removes the block; deciding the strategy should trade again is a separate act with its own evidence gate (INV3), and collapsing the two would let a breaker reset quietly promote something.
  • PUT /investors/broker/profiles/{profile_id}/connection · scope investors:writeConnectSeal a tenant's own broker credential. Fitinty never opens a brokerage account on anybody's behalf and never bills for broker access - the same rule as the affiliate networks and the tenant data feeds. What is different here is the sealing: once written, the application cannot read this back. Not "will not" - cannot. The column privilege does not exist.
  • POST /investors/broker/profiles/{profile_id}/connection/revoke · scope investors:writeRevokeRevoke the connection. The sealed credential stays in the row and stays unreadable.
  • POST /investors/broker/profiles/{profile_id}/place · scope investors:writePlaceThe real-order path. It exists, it is wired to the gate, and the gate says no. This is the function `is_live_execution_enabled` was written for and has never had. Having it here, refusing, is worth more than not having it: the refusal is now a tested behaviour rather than an absence somebody could fill in without noticing what they were bypassing.
  • POST /investors/broker/profiles/{profile_id}/preflight · scope investors:writePreflightShape the order this intent would become, show it, and send nothing. The point is that a tenant can see their strategy producing sane orders long before any of them could be real - and that the shaping is reviewable rather than taken on trust.
  • GET /investors/broker/profiles/{profile_id}/readiness · scope investors:readReadinessEvery gate, what it says, and everything that has been attempted.
  • POST /investors/combos/profiles/{profile_id} · scope investors:writeCreate ComboBuild and evaluate a package. Refuses the whole thing or none of it.
  • GET /investors/combos/profiles/{profile_id} · scope investors:readList Combos
  • POST /investors/combos/{combo_id}/fill · scope investors:writeFill ComboFill every leg, or none of them. All the legs go through `_apply_fill`, so the multiplier, the cash ledger and the fee model are INV14's and INV4's rather than a second implementation that drifts. One transaction: if any leg raises, the whole package rolls back, which is what all-or-nothing means when the alternative is being left holding the leg nobody wanted.
  • POST /investors/correlation/profiles/{profile_id}/snapshot · scope investors:writeSnapshotMeasure the book's real concentration and record what it was measured from.
  • GET /investors/correlation/profiles/{profile_id}/snapshots · scope investors:readHistory
  • POST /investors/cross-asset · scope investors:writeCreate Cross Asset Relationship
  • GET /investors/divergence/profiles/{profile_id} · scope investors:readHistory
  • POST /investors/divergence/profiles/{profile_id}/reconcile · scope investors:writeRun ReconcileCheck a strategy's claim against what the engine did, and record the attribution.
  • POST /investors/dividends/calendar · scope investors:writeRecordEnter a dividend. There is no feed to do this automatically and the schema says so.
  • PUT /investors/dividends/coverage · scope investors:writeAssert CoverageSay what is KNOWN about an instrument, so that silence stops being ambiguous.
  • GET /investors/dividends/feed/freshness · scope investors:readFreshnessReadable by everyone, because 'is anybody checking' is not an operational secret. INV18's distinction: an empty list of problems and a checker that stopped running look identical on a screen and mean opposite things.
  • GET /investors/dividends/feed/instruments/{symbol} · scope investors:readLatest For SymbolWhat the feed last concluded about one instrument, and why. Findings are readable by any authenticated user: that a company has never declared a dividend is a fact about the company, and knowing it says nothing about anybody's positions.
  • POST /investors/dividends/feed/run · scope investors:writeTrigger RunAsk the source now. Idempotent in effect: re-clearing an instrument just refreshes its date.
  • GET /investors/dividends/feed/runs · scope investors:readList Runs
  • GET /investors/dividends/profiles/{profile_id}/coverage · scope investors:readRead CoverageWhat has NOT been checked, first. A dividend list cannot display an absence.
  • GET /investors/dividends/profiles/{profile_id}/early-assignment · scope investors:readRead Risk
  • POST /investors/edge/concepts/{concept_id}/candidate · scope investors:writePropose CandidateTurn a concept into a strategy - `untested`, like any other idea. This is where the pipeline hands over. The strategy it creates has no privileges: it cannot propose trades until it is at least `backtested`, and it cannot reach `backtested` without surviving INV3's gate. A concept that came out of forty slices of our own journal is treated exactly like an idea somebody had in the shower, which is…
  • POST /investors/edge/concepts/{concept_id}/discard · scope investors:writeDiscardDrop a concept, with a reason. The most valuable row in the table. An idea abandoned for a stated reason stops the next person - who on a monthly loop is you - spending a week rediscovering it.
  • GET /investors/equity/my-holding · scope investors:readGet My Holding
  • GET /investors/execution/authorization · scope investors:readGet Authorization
  • POST /investors/execution/authorization · scope investors:writeRecord AuthorizationOwner-only. Records the Emperor's attestation that legal counsel AND a broker relationship confirm real-order go-live. This is the ONLY way the execution gate is set — no automated path creates it, and it never enables real orders on its own.
  • POST /investors/execution/authorization/revoke · scope investors:writeRevoke AuthorizationOwner-only. Revokes any active execution authorization (real orders revert to not-authorized).
  • GET /investors/execution/readiness · scope investors:readReadiness
  • POST /investors/features · scope investors:writeWrite FeatureStore a feature against the date it was knowable. Written once and never revised. Recomputing a 2019 feature with today's better data and overwriting it would make every backtest that read the old value unreproducible - and would improve the new one in a way that had nothing to do with the strategy.
  • GET /investors/features · scope investors:readRead FeaturesFeatures up to a date - never past it. `as_of` is a ceiling rather than a filter, because that is the only reading that is safe inside a backtest: asking for features "as of 2019" and getting a 2020 value is precisely the leak this table exists to prevent.
  • POST /investors/futures/contracts · scope investors:writeAdd ContractRegister a futures contract. Owner-only - a contract calendar is reference data.
  • GET /investors/futures/contracts · scope investors:readList ContractsThe contract calendar, with what is expiring soonest first.
  • POST /investors/futures/rolls · scope investors:writeRecord RollRecord a roll, deriving the factor from what the two contracts actually traded at. The factor is computed here rather than supplied, because a supplied factor is a number nobody can check against anything.
  • GET /investors/futures/{root}/continuous · scope investors:readRead ContinuousThe continuous series as it would have appeared on a given date. Default `as_of` is today, which is the series everybody means when they say "the continuous contract" - and asking for an older date is how you check whether a backtest was reading prices that existed at the time.
  • POST /investors/institutional-flows · scope investors:writeCreate Institutional Flow
  • GET /investors/integrations · scope investors:readList Integrations
  • GET /investors/intents/{intent_id}/decision · scope investors:readGet DecisionWhy this intent got the answer it got - every limit, including the ones it passed.
  • POST /investors/intents/{intent_id}/execute · scope investors:writeExecute IntentTurn an approved intent into a paper order, a fill, and a journal row. The one path from a decision to a position. Everything it refuses, it refuses by name.
  • GET /investors/kill-switch · scope investors:readRead Kill SwitchWhether platform trading is stopped, and why. Readable by anyone authenticated on purpose: being told why trading stopped is not privileged information, and a tenant who cannot see it will open a support ticket instead.
  • POST /investors/kill-switch · scope investors:writeSet Kill SwitchThrow or clear the platform kill switch. Owner-only, named, and with a reason. A NEW ROW EACH TIME rather than a flag that flips. The switch is a log: what it was, who changed it, when and why - all of which the flag version throws away, and all of which is what somebody asks for afterwards. Nothing automatic can reach this. A kill switch thrown by code is a bug with a lever attached, and the ca…
  • GET /investors/live/audit · scope investors:readRead AuditThe compliance trail: every action on the live path, allowed or refused. Kept apart from `audit_events` deliberately - that is application telemetry with a telemetry retention policy, and this is the record somebody asks for when it matters.
  • GET /investors/live/disclosures · scope investors:readRead DisclosuresThe disclosures as they currently read, with the hash of each.
  • GET /investors/live/gate · scope investors:readRead GateEvery part of the live gate, and what each says. Readable by anyone authenticated: whether this platform is permitted to trade live is not privileged information, and a tenant who cannot see it has to take it on trust.
  • PUT /investors/live/profiles/{profile_id}/disclosures · scope investors:writeAcknowledgeRecord that a named person read the current text of one disclosure.
  • GET /investors/live/profiles/{profile_id}/disclosures · scope investors:readRead Profile DisclosuresWhat this profile has acknowledged, and what is outstanding.
  • POST /investors/live/signoff · scope investors:writeRecord SignoffRecord one party's sign-off, in their own words, under their own account. Owner-only, and the gate then requires the two rows to come from DIFFERENT accounts - so counsel needs an account of their own. "The owner records what counsel told them" deliberately does not open this.
  • POST /investors/live/signoff/{signoff_id}/revoke · scope investors:writeRevoke SignoffWithdraw a sign-off. Closing the gate is always available to one person - only opening it needs two.
  • GET /investors/macro · scope investors:readMacro Snapshot
  • GET /investors/market-data-sources · scope investors:readList Market Data Sources
  • POST /investors/market-data/add · scope investors:writeAdd InstrumentOwner-only: add ANY symbol on demand. Resolves the right free provider, fetches its bars immediately, and stores it as shared reference data so it appears everywhere (Live prices, charts, screener). DATA ONLY — no broker, no orders.
  • POST /investors/market-data/backfill-crypto · scope investors:writeBackfill CryptoOwner-only: pull deep daily history for the crypto instruments from permissive venues. Market data is platform-curated reference data (migration 099: owner-only write). Every source used here is `redistribution = permitted` in migration 325 - so unlike the rest of our bars, these may actually be shown to a tenant.
  • POST /investors/market-data/backfill-fx · scope investors:writeBackfill FxFill the FX gap from the ECB. Owner-only: market data is platform-curated reference data (migration 099). Unlike our other non-crypto sources this one is `redistribution = permitted`, so these bars may actually be shown to a tenant.
  • GET /investors/market-data/chart-bars · scope investors:readChart BarsEvery instrument's full daily OHLCV series, ascending by date, keyed by symbol — shaped for TradingView Lightweight Charts. Read-only shared reference data (migration 099, select USING(true)). LICENCE-GATED (migration 325). Holding a bar and being allowed to show it to a tenant are different permissions, and this route is the one that hands data to a tenant. A bar whose source forbids redistribut…
  • GET /investors/market-data/coverage · scope investors:readCoverageWhat this platform can honestly claim, by asset class and by licence. Built because "we have 11,306 bars" was a true sentence that answered no useful question. The number that matters is how many of them we are allowed to use, and for what.
  • DELETE /investors/market-data/instruments/{instrument_id} · scope investors:writeRemove InstrumentOwner-only: remove a dynamically-added instrument (provider_symbol IS NOT NULL) and its bars. The original seeded core set can't be removed here.
  • GET /investors/market-data/intraday · scope investors:readMarket IntradayOn-demand intraday OHLC for one symbol. Crypto = Alpaca 1-min (keyless); stocks/ETFs/FX = Twelve Data 5-min; futures/indices aren't free intraday (supported=false).
  • GET /investors/market-data/latest · scope investors:readLatest PricesLatest close per instrument, with the real source label — readable by any authenticated user (market data is shared reference data, migration 099).
  • GET /investors/market-data/quotes · scope investors:readMarket QuotesLatest price per instrument for the auto-refreshing Live Quotes panel. Crypto is real-time (Alpaca); everything else is its latest stored close (labeled EOD). Readable by any user.
  • POST /investors/market-data/refresh · scope investors:writeRefresh Market DataOwner-only: pull real delayed EOD bars for the seeded instruments from the active provider and upsert them into market_bar. Market data is platform-curated reference data (migration 099: owner-only write).
  • GET /investors/market-data/search · scope investors:readSearch SymbolsSearch the global stock/ETF universe (Twelve Data symbol_search). US listings (USD) add via Twelve Data; international listings (London/Tokyo/Frankfurt/…) add under their Yahoo symbol (VOD.L, 7203.T, …) — Fitinty is global. FX/crypto pairs are added by exact symbol via /add.
  • GET /investors/market-instruments · scope investors:readList Market Instruments
  • GET /investors/mine · scope investors:readGet My Profile Endpoint
  • POST /investors/monte-carlo · scope investors:writeCreate Monte Carlo Run
  • GET /investors/news · scope investors:readMarket News
  • POST /investors/options/contracts · scope investors:writeCreate ContractRegister an option contract, and the instrument row that carries it.
  • GET /investors/options/profiles/{profile_id}/positions/{instrument_id} · scope investors:readRead Position
  • PUT /investors/options/profiles/{profile_id}/undefined-risk · scope investors:writeAuthorize Undefined RiskPermit this account to hold a position whose loss has no upper bound. Revoke-then-insert rather than upsert, so the previous authorization survives as a row. What was permitted, by whom, and until when is the question this table exists to answer later.
  • GET /investors/options/profiles/{profile_id}/undefined-risk · scope investors:readRead Undefined Risk
  • POST /investors/options/structure · scope investors:writeAnalyse StructureWhat is the worst this combination can do, and does it have a floor at all?
  • POST /investors/options/synthesise-chain · scope investors:writeSynthesise ChainBuild a clearly-labelled SYNTHETIC option chain, for the paper loop only. There is no permissively licensed options chain with implied volatility. Every retail feed that carries one licenses it for personal use, exactly as INV9 found for equities, and chains are priced higher than bars everywhere. So rather than leave the whole instrument class untestable, this generates one from Black-Scholes ov…
  • POST /investors/paper-orders · scope investors:writeCreate Paper Order
  • POST /investors/paper-orders/{order_id}/cancel · scope investors:writeCancel Paper Order
  • POST /investors/paper-orders/{order_id}/continue-fill · scope investors:writeContinue Fill Paper Order
  • GET /investors/paper-orders/{order_id}/fills · scope investors:readList Paper Fills
  • POST /investors/paper-orders/{order_id}/submit · scope investors:writeSubmit Paper Order
  • POST /investors/portfolio-positions · scope investors:writeUpsert Portfolio Position
  • POST /investors/portfolios · scope investors:writeCreate Portfolio
  • GET /investors/portfolios/{portfolio_id}/positions · scope investors:readList Portfolio Positions
  • POST /investors/price-alerts · scope investors:writeCreate Alert
  • POST /investors/price-alerts/{alert_id}/cancel · scope investors:writeCancel Alert
  • GET /investors/profiles/{profile_id}/book · scope investors:readRead BookPositions marked to the last close, beside what they cost - and what closing them would cost.
  • GET /investors/profiles/{profile_id}/cash · scope investors:readRead CashThe cash balance, the ledger behind it, and whether the two agree. Reconciliation is a query rather than an argument: a balance that can drift from its own ledger is a balance nobody can check.
  • PUT /investors/profiles/{profile_id}/circuit-breakers · scope investors:writeSet BreakersSet the thresholds. Append-only: the previous version stays readable, so what the breakers were when one tripped is always answerable.
  • GET /investors/profiles/{profile_id}/circuit-breakers · scope investors:readRead BreakersThe thresholds, whether anything is currently tripped, and every trip on record.
  • POST /investors/profiles/{profile_id}/circuit-breakers/sweep · scope investors:writeSweepEvaluate the account breakers now, without waiting for somebody to try to trade. Nothing depends on this having run - the same checks happen on every intent. This exists so a trip can be surfaced before the next attempt rather than at it.
  • POST /investors/profiles/{profile_id}/costs · scope investors:writeAdd CostRecord work done on this profile's behalf that the platform did not measure itself.
  • PUT /investors/profiles/{profile_id}/data-connections · scope investors:writeConnect Data FeedConnect this profile's OWN market-data subscription. The licence then sits between the tenant and their provider - we are a client they pointed at their own account, not a redistributor. That is the clean answer to the restriction that makes our platform-supplied feeds non-displayable, and it is also what a trader who already pays for data actually wants. The credential is stored so we can call …
  • GET /investors/profiles/{profile_id}/data-connections · scope investors:readList Data FeedsThe profile's connections. Credentials are never included - only whether one is set.
  • GET /investors/profiles/{profile_id}/edge · scope investors:readRead PipelineThe pipeline's state: what the journal seems to say, and what has been made of it.
  • POST /investors/profiles/{profile_id}/edge/concepts · scope investors:writeSynthesiseTurn hints into a hypothesis - or refuse, when the hints do not carry one. The refusal is the feature. A pipeline that always produces a concept is a pipeline that produces confident descriptions of noise, and somebody will act on those.
  • POST /investors/profiles/{profile_id}/edge/extract · scope investors:writeExtract HintsSlice the journal and record what it seems to say. Append-only and deliberately not deduplicated: running this a month apart produces two hints on one dimension with different sample sizes, and the pair says more than either. A slice that strengthens as n grows is a different object from one that evaporates.
  • PUT /investors/profiles/{profile_id}/evidence-policy · scope investors:writeSet Evidence PolicySet this profile's evidence thresholds. The bounds are deliberate: a deflated Sharpe below 0.5 or a PBO above 0.5 would be a gate that admits results indistinguishable from chance, and a threshold that cannot refuse anything is not a threshold.
  • GET /investors/profiles/{profile_id}/journal · scope investors:readRead JournalEvery fill, with the rules that caused it and the ruling that permitted it.
  • POST /investors/profiles/{profile_id}/journal/attribute · scope investors:writeAttributeAttach a regime label to every fill that has none. Only rows without one: relabelling would make the attribution behind a past review unreproducible, and a review whose numbers move underneath it is not evidence of anything. The label is set through a SECURITY DEFINER function that can touch nothing else on the row, because an UPDATE grant on the journal would let some future code path amend a re…
  • GET /investors/profiles/{profile_id}/journal/attribution · scope investors:readRead AttributionWhere the realised P&L went, by cause, over the journal's own round trips (IN7).
  • PUT /investors/profiles/{profile_id}/machine-rate · scope investors:writeSet RateSay what an hour of your machine is worth. Until you do, cost is reported in seconds only.
  • PUT /investors/profiles/{profile_id}/paper-account · scope investors:writeOpen Paper AccountDeclare how big this paper account is. Every percentage limit is measured against it. Declared rather than defaulted: a starting capital nobody chose would end up inside every risk calculation this profile ever makes, and nobody would remember it was a guess.
  • POST /investors/profiles/{profile_id}/reviews · scope investors:writeWrite ReviewRecord a period review, with the tear sheet as it stood when it was written. Append-only on purpose: the point of a review is to be able to read back what somebody believed BEFORE they knew how the next month went. A review that can be edited afterwards records only the opinion that turned out well.
  • GET /investors/profiles/{profile_id}/reviews · scope investors:readList ReviewsPast reviews, newest first - including the ones whose hypothesis turned out wrong.
  • GET /investors/profiles/{profile_id}/risk-decisions · scope investors:readList Decisions
  • PUT /investors/profiles/{profile_id}/risk-limits · scope investors:writeSet LimitsSet the fence. The previous version is superseded, never overwritten.
  • GET /investors/profiles/{profile_id}/risk-limits · scope investors:readGet Limits
  • GET /investors/profiles/{profile_id}/tear-sheet · scope investors:readTear SheetThe performance report. Equally usable by somebody deciding to allocate and somebody deciding to shut the thing down - if it reads better for one of those, it is selling.
  • GET /investors/profiles/{profile_id}/trading-strategies · scope investors:readList TradingThe profile's strategies, seen through what they are allowed to DO. Deliberately a different view from investors_research's list, not a second copy of it: that one answers "what ideas do we have", this one answers "what is trading, and how".
  • PUT /investors/profiles/{profile_id}/tradingview-symbols · scope investors:writeMap SymbolMap a TradingView ticker to one of our instruments. TradingView's spelling is not always ours - BTCUSD there, BTC-USD here - and an unmapped ticker is refused rather than guessed.
  • GET /investors/profiles/{profile_id}/tradingview-symbols · scope investors:readList Symbol Map
  • GET /investors/profiles/{profile_id}/true-pnl · scope investors:readTrue PnlTrading profit, minus what it cost to produce it. The number INV7's tear sheet does not compute, and the one that decides whether a strategy is worth running at all.
  • POST /investors/research-notes · scope investors:writeCreate Research Note
  • POST /investors/research-notes/{note_id}/ai-draft · scope investors:writeAi Draft Research NoteAI drafts model_outputs (a synthesis of the note's own thesis/evidence/counter-evidence) - advisory only, always labeled ai_generated, review_status stays 'draft' until a human explicitly marks it reviewed. Matches this project's 'AI drafts, never authoritative' pattern used everywhere else.
  • PATCH /investors/research-notes/{note_id}/review · scope investors:writeReview Research Note
  • POST /investors/risk-policies · scope investors:writeCreate Risk Policy
  • POST /investors/sentiment-observations · scope investors:writeCreate Sentiment ObservationLLM-assisted sentiment labeling - advisory only, always ai_generated=true and human_reviewed=false until a reviewer explicitly confirms it. Matches spec section 20: 'LLM sentiment is advisory.'
  • PATCH /investors/sentiment-observations/{obs_id}/review · scope investors:writeReview Sentiment Observation
  • POST /investors/sequencer/combos/{combo_id}/sequence · scope investors:writeRun SequenceWalk a legged package one leg at a time. Resumes a partial one from where it stopped.
  • POST /investors/sequencer/combos/{combo_id}/unwind · scope investors:writeRun Unwind
  • GET /investors/sequencer/stuck · scope investors:readStuckEverything halfway through becoming something else. The query somebody runs when they suspect a sequencer died. It reports elapsed time rather than a verdict, because a sequencer that is genuinely mid-walk and one whose process died look identical from the database and only the clock separates them.
  • POST /investors/signals · scope investors:writeCreate Signal
  • PATCH /investors/signals/{signal_id}/status · scope investors:writeUpdate Signal Status
  • GET /investors/sizer/profiles/{profile_id}/proposals · scope investors:readHistory
  • POST /investors/sizer/profiles/{profile_id}/propose · scope investors:writeProposeHow much, and why that much. Records the proposal, because it is advice with a timestamp.
  • POST /investors/strategies · scope investors:writeCreate Strategy
  • POST /investors/strategies/{sid}/backtest · scope investors:writeRun Evidence BacktestRun the strategy's spec under walk-forward validation and judge the result. This is the only thing that can make `backtested` true. It is a trial whether it passes or not - the row is written before the verdict is known and cannot be deleted, which is what stops the trial count from quietly becoming the count of runs somebody liked.
  • GET /investors/strategies/{sid}/evidence · scope investors:readStrategy EvidenceEvery evidence run this strategy has, newest first, and whether any of them still stands. The workspace needs both halves of that. A strategy with four failed runs and one pass is in a different position from one with a single pass, and a strategy whose pass sits on a spec that has since been edited looks identical to a passing one until you ask.
  • POST /investors/strategies/{sid}/instruments · scope investors:writeSet InstrumentsDeclare what this strategy trades. Required before it may propose anything. Declared, never inferred: a strategy that claims to trade anything has been tested on nothing, and a later backtest is only an honest test of the claim if the claim was made first.
  • POST /investors/strategies/{sid}/intents · scope investors:writeSubmit IntentThe strategy proposes a trade. Nothing else happens. The intent lands `pending` and stays there. That is the shape the system is built to, not an unfinished feature: the risk authority is the only thing that may approve, size or refuse, and it arrives in INV2.
  • GET /investors/strategies/{sid}/intents · scope investors:readList Intents
  • GET /investors/strategies/{sid}/passport · scope investors:readPassportThe whole life of a strategy, in order, with names on it.
  • PUT /investors/strategies/{sid}/spec · scope investors:writeSet SpecGive a strategy a machine-runnable spec beside its prose. The prose stays authoritative for what the strategy MEANS - a reviewer approved words, not JSON. The spec is what actually runs. Keeping them separate is why a passing backtest cannot quietly redefine the strategy somebody signed off. Superseding a spec invalidates any promotion that rested on it, because evidence is about the thing that …
  • POST /investors/strategies/{sid}/trade-mode · scope investors:writeSet Trade ModeSwitch a proven strategy between manual and auto, at will. No ceremony: no reason required, no approval, flip it as often as the day demands - that is what "at will" has to mean or people work around it. But never silent, because "who turned this on, and when" is the first question anybody asks after a bad day, and a switch with no answer to that is a switch nobody will trust enough to use. One …
  • POST /investors/strategies/{sid}/trading-state · scope investors:writeSet Trading StateMove a strategy's trading state - by a named person, with a reason, on the record. `backtested` and `paper` are no longer claims. INV3 built the gate, so moving into either now requires a walk-forward run that survived the profile's evidence thresholds - and the passport records WHICH run, so the promotion can be re-read against the evidence that justified it. `halted` and `untested` stay claim-…
  • POST /investors/strategies/{sid}/webhook · scope investors:writeCreate WebhookIssue a TradingView webhook token for this strategy. The token is returned ONCE. It is stored hashed and there is nowhere to read it from afterwards - if it is lost, rotate rather than recover. Rotating supersedes the old one immediately, so an alert still configured with it starts failing rather than quietly continuing to work.
  • GET /investors/strategies/{sid}/webhook · scope investors:readRead WebhookThe webhook's state and its recent deliveries - including everything it refused.
  • POST /investors/strategies/{sid}/webhook/revoke · scope investors:writeRevoke WebhookTurn the webhook off. Alerts still configured with it are refused and recorded.
  • PATCH /investors/strategies/{strategy_id} · scope investors:writeUpdate Strategy
  • GET /investors/strategies/{strategy_id}/versions · scope investors:readList Strategy Versions
  • POST /investors/sweep/run · scope investors:writeTriggerRun a sweep now. The timer runs the same function; this exists for a person in a hurry.
  • GET /investors/sweep/status · scope investors:readStatusWhen did the heartbeat last beat, and what did it find. THE ALERT IS THE STALENESS. No threshold is stored anywhere - the caller knows what it considers too long far better than this schema does, and a threshold in a table is one more thing to configure wrongly and then trust. THE HEARTBEAT IS PUBLIC AND THE FINDINGS ARE NOT. Anyone authenticated may learn that the sweep ran, because a tenant wh…
  • PUT /investors/tenant-zero · scope investors:writeDesignateDesignate the profile Fitinty's own fund runs on. This grants nothing. It is a label, and the attestation is typed rather than defaulted so that somebody proposing an exemption later has to read what they are contradicting.
  • GET /investors/tenant-zero · scope investors:readRead Tenant ZeroThe designation, and every gate answering for it. Readable by anyone authenticated on purpose. A platform that trades its own fund on the rails it sells should say which profile that is; one that hides it is making a different claim.
  • POST /investors/universe/adopt · scope investors:writeAdoptListed -> core, by the owner's hand, and the first bars fetched NOW through the standing fetchers. Refused in words when no licensed bars come back: a core instrument with no data would sit in every picker as a blank.
  • GET /investors/universe/agents/board · scope investors:readRead Board
  • GET /investors/universe/agents/{symbol} · scope investors:readRead One
  • GET /investors/universe/alpha-score · scope investors:readRead Alpha
  • GET /investors/universe/autopilot · scope investors:readRead Autopilot
  • PUT /investors/universe/budget/{source_id} · scope investors:writeSet BudgetThe dial. A budget is a limit on what the PLATFORM spends; it never buys anything.
  • GET /investors/universe/candidates · scope investors:readRead Candidates
  • POST /investors/universe/debate/{symbol} · scope investors:writeDebate NowA workspace admin asks for a debate over their own dial's candidate. Once a day per instrument.
  • GET /investors/universe/debates · scope investors:readList Debates
  • GET /investors/universe/events · scope investors:readCalendar
  • GET /investors/universe/features/{symbol} · scope investors:readRead FeaturesThe newest feature row per name for one instrument - what an agent would read tonight.
  • GET /investors/universe/feed · scope investors:readRead FeedThe Whale Signals feed - one signal day, however many reads.
  • GET /investors/universe/fundamentals/core · scope investors:readCore FundamentalsEvery core equity's headline figures as knowable on the as-of date (today by default).
  • GET /investors/universe/fundamentals/{symbol} · scope investors:readOne Fundamentals
  • GET /investors/universe/listed · scope investors:readSearch ListedSearch what the platform KNOWS but has not adopted. Reading only; adoption is a door.
  • GET /investors/universe/money · scope investors:readRead Money
  • POST /investors/universe/run · scope investors:writeRun NowRun the nightly now, by the owner's hand. The timer runs the same function inside the sweep.
  • GET /investors/universe/runs · scope investors:readList Runs
  • GET /investors/universe/scoring-policy · scope investors:readRead Policy
  • PUT /investors/universe/scoring-policy · scope investors:writeSet My Policy
  • PUT /investors/universe/scoring-policy/platform · scope investors:writeSet Platform Policy
  • PUT /investors/universe/signal-subscription · scope investors:writeSet SubscriptionThe workspace opts in to the nightly digest, naming its channels and its consent in its own words. A channel that leaves the platform delivers only once the Emperor's counsel sign-off stands on it - the opt-in records the wish, the channel's gate decides.
  • DELETE /investors/universe/signal-subscription · scope investors:writeClear Subscription
  • GET /investors/universe/summary · scope investors:readRead Summary
  • POST /investors/watchlist-items · scope investors:writeAdd Watchlist Item
  • PATCH /investors/watchlist-items/{item_id}/status · scope investors:writeUpdate Watchlist Item Status
  • POST /investors/watchlists · scope investors:writeCreate Watchlist
  • GET /investors/watchlists/{watchlist_id}/items · scope investors:readList Watchlist Items
  • POST /investors/whale-observations · scope investors:writeCreate Whale Observation
  • GET /investors/{profile_id} · scope investors:readGet Profile
  • GET /investors/{profile_id}/alpha-audit · scope investors:readList Alpha Audit Entries
  • GET /investors/{profile_id}/backtests · scope investors:readList Backtests
  • GET /investors/{profile_id}/cross-asset · scope investors:readList Cross Asset Relationships
  • GET /investors/{profile_id}/disclosure-policies · scope investors:readList Disclosure Policies
  • GET /investors/{profile_id}/institutional-flows · scope investors:readList Institutional Flows
  • GET /investors/{profile_id}/members · scope investors:readList Members
  • POST /investors/{profile_id}/members · scope investors:writeAdd Member
  • GET /investors/{profile_id}/monte-carlo · scope investors:readList Monte Carlo Runs
  • GET /investors/{profile_id}/paper-orders · scope investors:readList Paper Orders
  • GET /investors/{profile_id}/paper-positions · scope investors:readList Paper Positions
  • GET /investors/{profile_id}/portfolios · scope investors:readList Portfolios
  • GET /investors/{profile_id}/price-alerts · scope investors:readList Alerts
  • GET /investors/{profile_id}/research-notes · scope investors:readList Research Notes
  • GET /investors/{profile_id}/risk-policies · scope investors:readList Risk Policies
  • GET /investors/{profile_id}/sentiment-observations · scope investors:readList Sentiment Observations
  • GET /investors/{profile_id}/signals · scope investors:readList Signals
  • GET /investors/{profile_id}/strategies · scope investors:readList Strategies
  • GET /investors/{profile_id}/war-room · scope investors:readGet War Room
  • GET /investors/{profile_id}/watchlists · scope investors:readList Watchlists
  • GET /investors/{profile_id}/whale-observations · scope investors:readList Whale Observations
  • POST /personal-credit/coach/analyze · scope investors:writeAnalyzeFull educational walkthrough of the latest sandbox pull: factor standings + legitimate improvement actions. Persisted so the user can revisit it.
  • POST /personal-credit/coach/ask · scope investors:writeAskOne educational question, grounded on the latest sandbox pull. Persisted like analyses.
  • GET /personal-credit/coach/sessions · scope investors:readList Sessions
  • GET /personal-credit/dispute-sandbox/cases · scope investors:readList Cases
  • POST /personal-credit/dispute-sandbox/cases · scope investors:writeCreate CaseAccuracy-first triage BEFORE anything else exists on the case. An item the agent assesses as accurate gets an honest educational explanation and a closed case - no letter path.
  • GET /personal-credit/dispute-sandbox/cases/{case_id} · scope investors:readGet Case
  • POST /personal-credit/dispute-sandbox/cases/{case_id}/draft-letter · scope investors:writeDraft Letter
  • POST /personal-credit/dispute-sandbox/cases/{case_id}/responses · scope investors:writeUpload ResponseThe consumer pastes what came back (in the sandbox: any simulated bureau reply). The agent classifies the outcome and decides the next move, with its rationale stored verbatim.
  • POST /personal-credit/dispute-sandbox/letters/{letter_id}/simulate-mailing · scope investors:writeSimulate MailingStamps the simulated consumer-mailed moment. Nothing is sent - in the future real program, this step is the consumer's own real action (or a counsel-approved send path).
  • GET /personal-credit/monitoring/authorization · scope investors:readGet Authorization
  • POST /personal-credit/monitoring/authorization · scope investors:writeRecord AuthorizationOwner-only. Records the Emperor's attestation that legal counsel AND a real provider relationship confirm real personal-credit pulls going live. This is the ONLY way the authorization gate is ever set — no automated path creates it, and it never enables real pulls on its own.
  • POST /personal-credit/monitoring/authorization/revoke · scope investors:writeRevoke AuthorizationOwner-only. Revokes any active provider authorization (real pulls, if they ever existed, revert to not-authorized).
  • GET /personal-credit/monitoring/my-consent · scope investors:readGet My Consent
  • POST /personal-credit/monitoring/my-consent · scope investors:writeGrant My ConsentThe real FCRA 'permissible purpose' consent click — a real person consenting to a real review of THEIR OWN credit file, and nothing else (purpose is DB-fixed to 'self'). Never bulk-applied; one user's own action, every time.
  • POST /personal-credit/monitoring/my-consent/revoke · scope investors:writeRevoke My Consent
  • GET /personal-credit/monitoring/my-pulls · scope investors:readList My Pulls
  • GET /personal-credit/monitoring/readiness · scope investors:readReadiness
  • POST /personal-credit/monitoring/sandbox-pull · scope investors:writeRequest Sandbox PullRequires an active real consent record first — the same 'no real action without consent' gate every other personal-data flow in this codebase enforces. Always runs the internal mock (see _run_mock_pull's own docstring for exactly why), regardless of whether a provider is configured — this never contacts a real bureau, live mode or not.
Farms - 141 doors - farms:read, farms:write

Land, sensors and what grows on them.

  • GET /farms · scope farms:readList Farms
  • POST /farms · scope farms:writeCreate Farm
  • POST /farms/consignments/{consignment_id}/accept · scope farms:writeAccept ConsignmentThe HUB's word: its retail price, and the lot on ITS store. Only the hub's staff may accept.
  • POST /farms/consignments/{consignment_id}/decline · scope farms:writeDecline Consignment
  • POST /farms/consignments/{consignment_id}/withdraw · scope farms:writeWithdraw ConsignmentThe FARM's hand, at will: what is unsold comes back; the hub's product carries the rest or comes down.
  • POST /farms/crew-members/{member_id}/leave · scope farms:writeMember Leaves
  • POST /farms/crews/{crew_id}/close · scope farms:writeClose Crew
  • POST /farms/crews/{crew_id}/members · scope farms:writeAdd MemberA crew member IS an employee of the farm's business - the one roster, never a farm copy.
  • POST /farms/crop-cycles · scope farms:writeCreate Crop Cycle
  • PATCH /farms/crop-cycles/{cycle_id} · scope farms:writeUpdate Crop Cycle
  • POST /farms/devices · scope farms:writeRegister Farm Device
  • POST /farms/devices/{device_id}/telemetry/synthetic · scope farms:writeCapture Synthetic TelemetryGenerates one real synthetic telemetry reading for a registered device - deterministic randomized values within realistic ranges per measurement type, matching the spec's own 'mock-first, separately gated' posture (section 11: real sensor deployment stays out of Core).
  • POST /farms/digital-twin · scope farms:writeCreate Digital Twin Asset
  • POST /farms/fields · scope farms:writeCreate Field
  • PATCH /farms/fields/{field_id} · scope farms:writeUpdate Field
  • POST /farms/harvest-plans · scope farms:writeCreate Harvest Plan
  • POST /farms/harvest-plans/{plan_id}/ai-propose · scope farms:writeAi Propose Harvest ReadinessAI drafts a harvest-readiness assessment from the linked crop cycle's real recorded observations/health notes - advisory only, never auto-approved, matching this project's 'AI drafts, never authoritative' pattern used everywhere else.
  • PATCH /farms/harvest-plans/{plan_id}/status · scope farms:writeSet Harvest Plan Status
  • GET /farms/hubs/list · scope farms:readHubs
  • GET /farms/integrations · scope farms:readList Integrations
  • POST /farms/inventory · scope farms:writeCreate Inventory Item
  • PATCH /farms/inventory/{item_id} · scope farms:writeUpdate Inventory Item
  • PATCH /farms/leads/{lead_id} · scope farms:writeSet Lead Statusengaged starts the response clock ONCE (first_contact_at); closed needs a reason; the moat measures from these words, so a status is a fact the farm states, never a guess.
  • POST /farms/livestock · scope farms:writeCreate Livestock
  • PATCH /farms/livestock/{livestock_id} · scope farms:writeUpdate Livestock
  • POST /farms/logistics-plans · scope farms:writeCreate Farm To Table Plan
  • POST /farms/logistics-plans/{plan_id}/preview-route · scope farms:writePreview RouteProduces a real, deterministic route-preview summary from the plan's own recorded fields - explicitly a preview/simulation, never a real dispatch (spec section 18: 'It may not dispatch real vehicles or promise delivery').
  • PATCH /farms/logistics-plans/{plan_id}/status · scope farms:writeSet Farm To Table Plan Status
  • POST /farms/maintenance · scope farms:writeCreate Maintenance Record
  • PATCH /farms/maintenance/{record_id}/status · scope farms:writeSet Maintenance Status
  • GET /farms/marketplace-bridge-check · scope farms:readMarketplace Bridge Check
  • GET /farms/mine · scope farms:readGet My Farm Endpoint
  • GET /farms/my/asks · scope farms:readMy Asks Door
  • GET /farms/my/orders · scope farms:readMy Orders
  • POST /farms/my/region-asks · scope farms:writeMy Region Ask
  • GET /farms/my/region-asks · scope farms:readMy Region Asks
  • GET /farms/my/rooms · scope farms:readGet My Rooms
  • GET /farms/my/shares · scope farms:readMy Shares
  • GET /farms/my/standing-orders · scope farms:readMy Standing Orders
  • POST /farms/my/standing-orders · scope farms:writeCreate Standing OrderThe buyer's own row (their cursor, the buyer arm), and the farm's lead for it - once.
  • PATCH /farms/my/standing-orders/{order_id} · scope farms:writeSet Standing Order Status
  • GET /farms/my/visits · scope farms:readMy VisitsEvery open offer with its next real slots, and my own bookings.
  • POST /farms/offers/{offer_id}/withdraw · scope farms:writeWithdraw Offer
  • POST /farms/orders/{order_id}/pay · scope farms:writePay OrderThe buyer's pay door: a Stripe Checkout Session ON THE FARM'S OWN ACCOUNT, Fitinty's cut as the application fee from the one ladder (the farms rate), pending-before-Stripe.
  • POST /farms/partner-hubs · scope farms:writeCreate Partner Hub
  • POST /farms/payments/webhook · scope farms:writeFarms Stripe WebhookThe pillar's own verified door, the Services shape exactly: signature or refusal, captured events only, idempotent by the pending->paid guard + the fee ledger's unique payment id.
  • POST /farms/pickup-points/{point_id}/active · scope farms:writeSet Pickup Point Active
  • POST /farms/produce-batches · scope farms:writeCreate Produce Batch
  • PATCH /farms/produce-batches/{batch_id} · scope farms:writeUpdate Produce Batch
  • POST /farms/produce-batches/{batch_id}/custody · scope farms:writeRecord Custody
  • POST /farms/produce-batches/{batch_id}/list · scope farms:writeList BatchThe farm's own bridge: this lot becomes a real product on the farm's store - a DRAFT unless `publish` is asked, in which case the go-live gate is asked first.
  • POST /farms/produce-batches/{batch_id}/lot-events · scope farms:writeRecord Lot Event DoorA person records custody. `withdrawn` and `recalled` need a reason and take the quantity off the lot (all of it when none is given); a listed product comes down and its stock follows - the store never sells a recalled lot. `adjusted` SETS what is available, and says why.
  • POST /farms/produce-batches/{batch_id}/publish · scope farms:writePublish Batch
  • POST /farms/produce-batches/{batch_id}/recall · scope farms:writeRecall LotFM3's own recall (the reason required, the quantity off, the product DOWN) plus the notice DRAFTED from the chain. Nothing is sent from here.
  • POST /farms/produce-batches/{batch_id}/unlist · scope farms:writeUnlist Batch
  • POST /farms/quality-records · scope farms:writeCreate Quality Record
  • PATCH /farms/quality-records/{record_id}/confirm · scope farms:writeConfirm Quality Record
  • POST /farms/quotes/accept/{token} · scope farms:writeAccept QuoteSIGNED-IN acceptance: the order is born on the ACCEPTOR'S OWN CURSOR (farm_order's WITH CHECK demands buyer_user_id = the caller - a signed-in person is ordering for themself); the quote and the ask then flip on the system cursor (the buyer cannot update the farm's rows, rightly).
  • POST /farms/quotes/{quote_id}/send · scope farms:writeSend QuoteSENT, and the accept link handed back - sending it stays the farm's hand. The ask is engaged: the farm answered.
  • POST /farms/quotes/{quote_id}/withdraw · scope farms:writeWithdraw Quote
  • POST /farms/recall-notices/{notice_id}/sent-by-hand · scope farms:writeNotice Sent By Hand
  • GET /farms/seasons/kits · scope farms:readSeason Kits
  • POST /farms/seasons/{season_id}/status · scope farms:writeSet Season StatusThe person's word: APPROVED stamps who; DISCARDED archives the season's cycles and cancels its open tasks (never deletes); CLOSED ends an approved season.
  • POST /farms/share-plans/{plan_id}/join · scope farms:writeJoin ShareSIGNED IN. A Stripe subscription session ON THE FARM'S OWN ACCOUNT, Fitinty's cut as a percent of each invoice from the one ladder; the farm that cannot charge is refused in words BEFORE any row is born.
  • POST /farms/share-plans/{plan_id}/status · scope farms:writeSet Plan StatusTHE FARM'S GO-LIVE for shares: OPEN puts the plan in front of every buyer's sourcing desk, so the ONE pay-to-open gate is asked here, in the body, before the flip. CLOSED stops new joins; the shares already held keep running until their holders end them.
  • POST /farms/shares/{share_id}/cancel · scope farms:writeCancel ShareThe holder ends their own share: the subscription is cancelled on the farm's account where one exists (the webhook's hand ends the record; it is ended here too, idempotently), a pending one just ends.
  • POST /farms/showcase · scope farms:writeUpload Showcase Asset
  • GET /farms/showcase/{asset_id}/download · scope farms:readDownload Showcase Asset
  • PATCH /farms/showcase/{asset_id}/status · scope farms:writeSet Showcase Asset StatusStop 18 - PUBLISHING is the pillar's go-live: a published showcase asset is readable by anyone (showcase_asset_select, 089), so the transition to 'published' takes the ONE pay-to-open gate in the function body (the standing lesson: a Depends misses plain-Python callers). Drafting stays free; the tenant's drafts are theirs and they keep.
  • POST /farms/storage-locations · scope farms:writeCreate Storage Location
  • POST /farms/tasks · scope farms:writeCreate Farm Task
  • PATCH /farms/tasks/{task_id}/assign · scope farms:writeAssign Farm TaskFM14: the ONE door that puts a person on a task or takes them off - the crew-scheduling kind and its reverse both come through here, so an assignment is never a row written beside the desk. A task already started keeps its person (a hand finishes what it began).
  • PATCH /farms/tasks/{task_id}/status · scope farms:writeSet Farm Task Status
  • POST /farms/visit-bookings/{booking_id}/cancel · scope farms:writeCancel VisitThe booker's own cancel. A paid visit stays a farm's refund by hand, said in words.
  • POST /farms/visit-bookings/{booking_id}/host-cancel · scope farms:writeHost CancelThe farm cancels a booking WITH A REASON (the booker reads it); a paid one is refunded by the farm's own hand, said.
  • POST /farms/visit-bookings/{booking_id}/pay · scope farms:writePay VisitThe booker's pay door: a Stripe Checkout Session ON THE FARM'S OWN ACCOUNT, the fee from the one ladder at the farms rate.
  • POST /farms/visits/{offer_id}/book · scope farms:writeBook Visit
  • GET /farms/visits/{offer_id}/slots · scope farms:readVisit Slots DoorThe open starts of one offer over the coming days - readable by anyone signed in or not: a time is not a person (the handover windows are public by design too).
  • POST /farms/visits/{offer_id}/status · scope farms:writeSet Visit StatusTHE FARM'S GO-LIVE for visits: OPEN puts the offer on every buyer's sourcing desk, so the one pay-to-open gate is asked here, in the body, before the flip. CLOSED stops new bookings; the bookings already made stand.
  • DELETE /farms/windows/{window_id} · scope farms:writeDelete Window
  • GET /farms/{farm_id} · scope farms:readGet Farm
  • PATCH /farms/{farm_id} · scope farms:writeUpdate Farm
  • PATCH /farms/{farm_id}/benchmark-consent · scope farms:writeSet Benchmark ConsentConsent is the exact words, signed; withdrawal blanks them and the next reading forgets the farm.
  • GET /farms/{farm_id}/community · scope farms:readFarm Community
  • POST /farms/{farm_id}/community/circle · scope farms:writeOpen CircleThe buyers' circle: the storefront's open room, born through the organ's one writer.
  • POST /farms/{farm_id}/community/exchange · scope farms:writeOpen ExchangeThe growers' exchange - knowledge, equipment, bulk buys - joined by anyone on a farm's membership.
  • POST /farms/{farm_id}/community/share-room · scope farms:writeOpen Share RoomThe CSA members' room pins to one of THIS farm's plans; one live room per plan.
  • POST /farms/{farm_id}/consignments · scope farms:writeConsignThe farm's offer, with the consent words RECORDED; born 'offered' on the farm's own cursor.
  • GET /farms/{farm_id}/consignments · scope farms:readConsignmentsBoth sides on one door: what this farm consigned (and is owed), what this hub received (and owes).
  • POST /farms/{farm_id}/costs · scope farms:writeRecord Cost
  • POST /farms/{farm_id}/crews · scope farms:writeCreate Crew
  • GET /farms/{farm_id}/crews · scope farms:readCrewsThe crews with their people (the roster's names, their training as the roster's own door reads it), the work records (capped), the period's pay lines.
  • GET /farms/{farm_id}/crop-cycles · scope farms:readList Crop Cycles
  • GET /farms/{farm_id}/devices · scope farms:readList Farm Devices
  • GET /farms/{farm_id}/digital-twin · scope farms:readList Digital Twin Assets
  • PATCH /farms/{farm_id}/directory · scope farms:writeSet Directory
  • GET /farms/{farm_id}/events · scope farms:readFarm EventsThe farm's events, newest first, with the seats taken - and the list says when it stops.
  • POST /farms/{farm_id}/events · scope farms:writeCreate Farm Event
  • POST /farms/{farm_id}/events/{event_id}/publish · scope farms:writePublish Farm EventPublishing IS the farm's go-live for an event: the pay-to-open gate in the body, then FitinEvents' own publish and open-to-public doors, so the seat's door is the platform's one ticket rail.
  • GET /farms/{farm_id}/fields · scope farms:readList Fields
  • POST /farms/{farm_id}/handoffs · scope farms:writeRecord Handoff
  • GET /farms/{farm_id}/handoffs · scope farms:readHandoffs
  • GET /farms/{farm_id}/harvest-plans · scope farms:readList Harvest Plans
  • GET /farms/{farm_id}/inventory · scope farms:readList Inventory
  • GET /farms/{farm_id}/leads · scope farms:readFarm Leads
  • GET /farms/{farm_id}/livestock · scope farms:readList Livestock
  • GET /farms/{farm_id}/logistics-plans · scope farms:readList Farm To Table Plans
  • GET /farms/{farm_id}/lot-events · scope farms:readList Lot Events
  • GET /farms/{farm_id}/maintenance · scope farms:readList Maintenance Records
  • GET /farms/{farm_id}/members · scope farms:readList Farm Members
  • POST /farms/{farm_id}/members · scope farms:writeAdd Farm Member
  • GET /farms/{farm_id}/moat · scope farms:readMoat
  • GET /farms/{farm_id}/money · scope farms:readFarm MoneyWhat this farm has taken on its own rail - read from the ONE fee ledger, never typed.
  • POST /farms/{farm_id}/offers · scope farms:writeMint Offer DoorThe hand's offer: a lead, a lot (or the best-matching listed one), the next three REAL open times.
  • GET /farms/{farm_id}/open-slots · scope farms:readOpen Slots Door
  • GET /farms/{farm_id}/partner-hubs · scope farms:readList Partner Hubs
  • GET /farms/{farm_id}/pickup-points · scope farms:readPickup Points
  • POST /farms/{farm_id}/pickup-points · scope farms:writeCreate Pickup Point
  • GET /farms/{farm_id}/produce-batches · scope farms:readList Produce Batches
  • GET /farms/{farm_id}/quality-records · scope farms:readList Quality Records
  • POST /farms/{farm_id}/quotes · scope farms:writeCreate QuoteThe farm's priced answer, born a DRAFT. Every line names a lot of this farm; the amount is the sum.
  • GET /farms/{farm_id}/quotes · scope farms:readList Quotes
  • GET /farms/{farm_id}/recall-notices · scope farms:readRecall Notices
  • GET /farms/{farm_id}/regional-demand · scope farms:readRegional Demand Door
  • GET /farms/{farm_id}/roster · scope farms:readRosterThe farm's people are the business's employees - hris_employee, never a farm copy.
  • GET /farms/{farm_id}/run-sheet · scope farms:readRun Sheet Door
  • GET /farms/{farm_id}/seasons · scope farms:readFarm SeasonsEvery season with its cycles' PLAN vs HARVEST (the batches that name the cycle), its costs by kind, its open tasks; and the farm's costs (capped). Revenue is the one money read, not here.
  • POST /farms/{farm_id}/seasons/propose · scope farms:writePropose SeasonA season DRAFT from the segment's kit: the cycles and the tasks with their dates from the kit's offsets. Deterministic from the data - a person approves, edits or discards it.
  • POST /farms/{farm_id}/share-plans · scope farms:writeCreate PlanA share plan, born a DRAFT. The window it names is one of THIS farm's handover windows.
  • GET /farms/{farm_id}/shares · scope farms:readFarm SharesThe plans, the members holding a share, and the cycles paid - the farm's own rows.
  • GET /farms/{farm_id}/showcase · scope farms:readList Showcase Assets
  • GET /farms/{farm_id}/standing-orders · scope farms:readFarm Standing Orders
  • GET /farms/{farm_id}/storage-locations · scope farms:readList Storage Locations
  • GET /farms/{farm_id}/tasks · scope farms:readList Farm Tasks
  • GET /farms/{farm_id}/telemetry · scope farms:readList Telemetry
  • GET /farms/{farm_id}/trace · scope farms:readTraceA recall is a query: by lot code, or by product over harvest dates.
  • GET /farms/{farm_id}/visit-bookings · scope farms:readFarm Visit BookingsThe offers and the bookings coming up - the farm's own rows.
  • POST /farms/{farm_id}/visits · scope farms:writeCreate VisitA visit offer, born a DRAFT. Price 0 is a free visit, said as such.
  • POST /farms/{farm_id}/windows · scope farms:writeAdd Window
  • POST /farms/{farm_id}/work-records · scope farms:writeRecord WorkQuantity x rate, earned computed ONCE here and never re-derived; the rate is the member's unless a different one is stated for this record (a different task pays a different rate).
  • POST /farms/{hub_farm_id}/payouts · scope farms:writeRecord PayoutRule 3: the hub RECORDS what it paid a farm by its own hand; no transfer is made from here.
Manufacturing - 155 doors - manufacturing:read, manufacturing:write

Products, plans and production runs.

  • GET /manufacturing · scope manufacturing:readList Factories
  • POST /manufacturing · scope manufacturing:writeCreate Factory
  • POST /manufacturing/bom-items · scope manufacturing:writeCreate Bom Item
  • DELETE /manufacturing/bom-items/{item_id} · scope manufacturing:writeDelete Bom Item
  • POST /manufacturing/boms · scope manufacturing:writeCreate Bom
  • GET /manufacturing/boms/{bom_id}/explode · scope manufacturing:readExplode
  • GET /manufacturing/boms/{bom_id}/items · scope manufacturing:readList Bom Items
  • POST /manufacturing/boms/{bom_id}/revise · scope manufacturing:writeRevise Bom
  • GET /manufacturing/boms/{bom_id}/revisions · scope manufacturing:readList Bom Revisions
  • PATCH /manufacturing/boms/{bom_id}/status · scope manufacturing:writeSet Bom Status
  • POST /manufacturing/capacity/{listing_id}/list · scope manufacturing:writeList CapacityTHE GO-LIVE: the declaration reaches the public page and the directory, so the ONE pay-to-open gate is asked here, in the body, before the word 'listed'.
  • POST /manufacturing/capacity/{listing_id}/withdraw · scope manufacturing:writeWithdraw Capacity
  • POST /manufacturing/cost-energy-estimates · scope manufacturing:writeCreate Cost Energy Estimate
  • POST /manufacturing/crew-members/{member_id}/leave · scope manufacturing:writeMember Leaves
  • POST /manufacturing/crews/{crew_id}/close · scope manufacturing:writeClose Crew
  • POST /manufacturing/crews/{crew_id}/members · scope manufacturing:writeAdd MemberA crew member IS an employee of the factory's business - the one roster, never a factory copy.
  • POST /manufacturing/digital-twins · scope manufacturing:writeCreate Digital Twin Project
  • PATCH /manufacturing/digital-twins/{twin_id}/status · scope manufacturing:writeSet Twin Status
  • POST /manufacturing/documents · scope manufacturing:writeUpload Document
  • GET /manufacturing/documents/{doc_id}/download · scope manufacturing:readDownload Document
  • PATCH /manufacturing/documents/{doc_id}/status · scope manufacturing:writeSet Document Status
  • POST /manufacturing/documents/{doc_id}/versions · scope manufacturing:writeAdd Document VersionA new file is a new version row; the document's current fields point at it; the old version stays a row (append-only) and its file stays on disk.
  • GET /manufacturing/documents/{doc_id}/versions · scope manufacturing:readList Document Versions
  • PATCH /manufacturing/documents/{document_id}/custody · scope manufacturing:writeSet CustodyWhose drawing this is, and how long it is kept.
  • POST /manufacturing/documents/{document_id}/licenses · scope manufacturing:writeWrite LicenseThe terms in the maker's words for one licensee, on a file that exists; born a DRAFT with no link and no price.
  • POST /manufacturing/documents/{document_id}/read-geometry · scope manufacturing:writeRead GeometryRead a design file already in this factory's own vault. It writes a READING and nothing else - no part, no bill, no routing until a person asks for them.
  • POST /manufacturing/engineering-changes · scope manufacturing:writeCreate Ecr
  • POST /manufacturing/engineering-changes/{ecr_id}/affected · scope manufacturing:writeAdd AffectedThe request names a row of its own factory - read on the caller's cursor, so a row that is not the factory's is 'not found', never linked.
  • GET /manufacturing/engineering-changes/{ecr_id}/affected · scope manufacturing:readList Affected
  • DELETE /manufacturing/engineering-changes/{ecr_id}/affected/{item_row_id} · scope manufacturing:writeRemove Affected
  • PATCH /manufacturing/engineering-changes/{ecr_id}/effective · scope manufacturing:writeSet EffectiveA date a person sets, on an approved request only.
  • PATCH /manufacturing/engineering-changes/{ecr_id}/status · scope manufacturing:writeSet Ecr Status
  • POST /manufacturing/geometry/{geometry_id}/propose · scope manufacturing:writePropose From GeometryDRAFT a part, a bill line and a routing from what the file said - each through the pillar's own existing writers, each a draft, none of them approved by this door.
  • GET /manufacturing/integrations · scope manufacturing:readList Integrations
  • POST /manufacturing/inventory · scope manufacturing:writeCreate Inventory Item
  • POST /manufacturing/inventory-locations · scope manufacturing:writeCreate Inventory Location
  • PATCH /manufacturing/leads/{lead_id} · scope manufacturing:writeSet Lead Statusengaged starts the response clock ONCE (first_contact_at); closed needs a reason. The moat measures from these words, so a status is a fact the factory states, never a guess.
  • POST /manufacturing/licenses/{license_id}/offer · scope manufacturing:writeOffer LicenseTHE GO-LIVE: the link is minted and the terms can be read by whoever holds it, so the ONE pay-to-open gate is asked here, in the body, before the word 'offered'.
  • POST /manufacturing/licenses/{license_id}/void · scope manufacturing:writeVoid License
  • POST /manufacturing/lots/{lot_id}/hand-off · scope manufacturing:writeHand Off LotTHE LAST LINK OF THE CHAIN: what was made LEFT this factory, and into whose hands. `handed_over` was a declared kind of this chain from MF5 with NO door that could write it - the trace could say what a lot was made from and never that it had gone. A recall answers "who has it" out of exactly these rows, so the gap was the one that mattered. IT RECORDS AND DOES NOT DISPATCH. Under his MF5 ruling …
  • POST /manufacturing/lots/{lot_id}/recall · scope manufacturing:writeRecall LotA recall takes the store product DOWN through MF3's own bridge - never a second writer - marks every lot this one reached, and DRAFTS the notice. NOTHING IS SENT: the words stand here and a person carries them out by their own hand.
  • GET /manufacturing/lots/{lot_id}/trace · scope manufacturing:readTraceWHAT WENT INTO THIS, and WHAT THIS WENT INTO. Both walks are queries over the genealogy rows; nothing is cached, so the answer is always the chain as it stands.
  • POST /manufacturing/manufacturability-reviews · scope manufacturing:writeCreate Manufacturability Review
  • POST /manufacturing/manufacturability-reviews/{review_id}/ai-assist · scope manufacturing:writeAi Assist Manufacturability ReviewAI drafts manufacturability findings from the review's own recorded criteria notes and the linked product's real fields - advisory only, a qualified reviewer must still approve, matching this project's 'AI drafts, never authoritative' pattern.
  • PATCH /manufacturing/manufacturability-reviews/{review_id}/status · scope manufacturing:writeSet Manufacturability Review Status
  • GET /manufacturing/mine · scope manufacturing:readGet My Factory Endpoint
  • GET /manufacturing/my/asks · scope manufacturing:readMy Asks DoorA person's own asks across makers - ownership decided in code from the account email, per row (the ONE ownership rule); the lead table itself stays the factory's alone.
  • GET /manufacturing/my/orders · scope manufacturing:readMy Orders
  • POST /manufacturing/my/region-asks · scope manufacturing:writeOpen Region Ask
  • GET /manufacturing/my/region-asks · scope manufacturing:readMy Region AsksThe buyer's own asks and where each one got to. NO MAKER'S ROWS are in this answer.
  • POST /manufacturing/orders/{order_id}/pay · scope manufacturing:writePay OrderThe buyer's pay door: a Stripe Checkout Session ON THE FACTORY'S OWN ACCOUNT, Fitinty's cut as the application fee from the one ladder (the manufacturing rate), pending-before-Stripe.
  • POST /manufacturing/partner-connections/{connection_id}/disconnect · scope manufacturing:writeDisconnectDISCONNECT ERASES. A flag on a kept secret is not a disconnection.
  • POST /manufacturing/partner-connections/{connection_id}/read · scope manufacturing:writeRead Through DoorTHE TRANSPORT IS NOT A DOOR PARAMETER. FastAPI reads an endpoint's arguments as the request's own, and a Callable has no schema - so the whole of /openapi.json answered 500 the first time this was written that way, which takes every screen down, not one. The act is a plain function (540's shape) and the guard injects a transport there with no network at all.
  • POST /manufacturing/partner-orders/{order_id}/approve · scope manufacturing:writeApprove OrderA PERSON'S WORD, with what it costs stated back to them. Approving still sends nothing.
  • POST /manufacturing/partner-orders/{order_id}/transmit · scope manufacturing:writeTransmit Order DoorThe only act that reaches outside, behind the lock. Same shape as the read above: the door takes no transport, so the schema stays buildable.
  • POST /manufacturing/partner-rfqs/{rfq_id}/answer · scope manufacturing:writeRecord AnswerTheir answer, as they gave it. A decline is an answer and is recorded as one - a partner who says no twice is a fact worth having.
  • POST /manufacturing/partner-rfqs/{rfq_id}/sent · scope manufacturing:writeMark SentA PERSON MARKS IT SENT. This platform puts nothing in a stranger's inbox (the standing real-communications law), so `sent` is a fact somebody records, and `sent_how` says how.
  • PATCH /manufacturing/partners/{partner_id} · scope manufacturing:writeUpdate Partner
  • POST /manufacturing/partners/{partner_id}/certifications · scope manufacturing:writeAdd CertificationA CLAIM, AND WHETHER ANYBODY SAW THE PAPER. `seen` records that a person looked at the certificate itself - an unverified claim stays a claim, and the register says which it is, because a certification nobody checked is exactly the kind of thing an auditor asks about.
  • POST /manufacturing/partners/{partner_id}/connect · scope manufacturing:writeConnect Partner513's SHAPE: the credential is encrypted the moment it arrives and never returned. Connecting reads nothing and commits nothing - it only makes the reads below possible.
  • POST /manufacturing/parts · scope manufacturing:writeCreate Part
  • PATCH /manufacturing/parts/{part_id}/cost · scope manufacturing:writeSet Part Cost
  • POST /manufacturing/payments/webhook · scope manufacturing:writeManufacturing Stripe WebhookThe pillar's own verified door, the Services shape exactly: signature or refusal, captured events only, idempotent by the pending->paid guard + the fee ledger's unique payment id.
  • POST /manufacturing/production-orders · scope manufacturing:writeCreate Production Order
  • POST /manufacturing/production-orders/{order_id}/complete · scope manufacturing:writeComplete OrderWhat was actually made. The reservations become consumption and the stock comes off; the finished-good LOT is minted and every source lot is LINKED to it, which is the only reason a recall can be answered later.
  • POST /manufacturing/production-orders/{order_id}/inspections · scope manufacturing:writeRecord InspectionA record at the routing's own inspection point. A fail or a rework states WHY - a verdict with no words teaches nobody anything, and it is the one somebody reads a year later.
  • GET /manufacturing/production-orders/{order_id}/inspections · scope manufacturing:readList Inspections
  • GET /manufacturing/production-orders/{order_id}/labour · scope manufacturing:readOrder Labour
  • GET /manufacturing/production-orders/{order_id}/materials · scope manufacturing:readList MaterialsWhat this order holds, and from which lot. A line with no lot named is shown as exactly that - the trace stops there, and the desk says so rather than implying a chain that does not exist.
  • POST /manufacturing/production-orders/{order_id}/release · scope manufacturing:writeRelease OrderA person releases the order to their own floor. The bill is exploded, what exists is RESERVED, and what is short is NAMED - never hidden, never a refusal.
  • POST /manufacturing/production-orders/{order_id}/simulate · scope manufacturing:writeSimulate Production OrderComputes a real simulated material/time/cost/energy summary from the order's own linked BOM items and routing steps - refuses to fabricate a number for data that doesn't exist, matching this project's 'no fake data' discipline.
  • POST /manufacturing/production-orders/{order_id}/start · scope manufacturing:writeStart Order
  • PATCH /manufacturing/production-orders/{order_id}/status · scope manufacturing:writeSet Production Order Status
  • POST /manufacturing/products · scope manufacturing:writeCreate Product
  • PATCH /manufacturing/products/{product_id} · scope manufacturing:writeUpdate Product
  • PATCH /manufacturing/products/{product_id}/cost-inputs · scope manufacturing:writeSet Product Cost Inputs
  • GET /manufacturing/products/{product_id}/cost-model · scope manufacturing:readRead Cost Model
  • POST /manufacturing/products/{product_id}/cost-model/record · scope manufacturing:writeRecord Cost ModelThe computed model recorded as an estimate row told apart from a typed one (computed = true, the breakdown kept), so the desk shows both and the plan's readers can compare them.
  • GET /manufacturing/products/{product_id}/export-line · scope manufacturing:readRead Export LineTHE DOWNLOAD DOOR'S OWN SENTENCE. The store delivers the file through its order-verified door; this is the words that belong beside it, which the seller sees on their own desk and the buyer sees on the page that hands over the file.
  • POST /manufacturing/products/{product_id}/list · scope manufacturing:writeList ProductThe factory's own bridge: this product becomes a real product on the factory's store - a DRAFT unless `publish` is asked, in which case the go-live gate is asked first.
  • POST /manufacturing/products/{product_id}/publish · scope manufacturing:writePublish Product
  • PATCH /manufacturing/products/{product_id}/statements · scope manufacturing:writeSet StatementsThe rights and export words on a product - a person's own, never derived and never drafted.
  • POST /manufacturing/products/{product_id}/unlist · scope manufacturing:writeUnlist Product
  • POST /manufacturing/prototypes · scope manufacturing:writeCreate Prototype Project
  • PATCH /manufacturing/prototypes/{proto_id}/status · scope manufacturing:writeSet Prototype Status
  • GET /manufacturing/pulse/readiness · scope manufacturing:readPulse ReadinessWHETHER THERE IS ANYTHING TO SELL YET. The plan's Pulse subscription is a subscription to this feed; it arrives when the feed has rows behind it, and this says plainly how far from that we are. No capability, plan row or pack ships before then.
  • POST /manufacturing/quotes/accept/{token} · scope manufacturing:writeAccept QuoteSIGNED-IN acceptance: the order is born on the ACCEPTOR'S OWN CURSOR (factory_order's WITH CHECK demands buyer_user_id = the caller); the quote and the ask then flip on the system cursor (the buyer cannot update the factory's rows, rightly).
  • POST /manufacturing/quotes/{quote_id}/send · scope manufacturing:writeSend QuoteSENT, and the accept link handed back - sending it stays the factory's hand. The ask is engaged: the factory answered, and the moat's response clock stops on this stamp.
  • POST /manufacturing/quotes/{quote_id}/withdraw · scope manufacturing:writeWithdraw Quote
  • PATCH /manufacturing/recall-notices/{notice_id}/delivered · scope manufacturing:writeMark DeliveredThe factory records that IT delivered the notice, and how. Fitinty still sent nothing - this is a person writing down what they did outside the platform (the mark-posted shape, GL3).
  • POST /manufacturing/routing-steps · scope manufacturing:writeCreate Routing Step
  • POST /manufacturing/routings · scope manufacturing:writeCreate Routing
  • PATCH /manufacturing/routings/{routing_id}/status · scope manufacturing:writeSet Routing Status
  • GET /manufacturing/routings/{routing_id}/steps · scope manufacturing:readList Routing Steps
  • POST /manufacturing/work-centers · scope manufacturing:writeCreate Work Center
  • PATCH /manufacturing/work-centers/{wc_id}/rates · scope manufacturing:writeSet Work Center Rates
  • PATCH /manufacturing/work-order-materials/{material_id}/lot · scope manufacturing:writeName The LotWHICH lot this line is taking from. Naming it is what lets a recall walk forward from a bad part to every finished good it reached.
  • GET /manufacturing/{factory_id} · scope manufacturing:readGet Factory
  • PATCH /manufacturing/{factory_id} · scope manufacturing:writeUpdate Factory
  • GET /manufacturing/{factory_id}/boms · scope manufacturing:readList Boms
  • GET /manufacturing/{factory_id}/capacity · scope manufacturing:readCapacity DeskEvery declaration this factory made, with the centre's name, and what a stranger reads today.
  • POST /manufacturing/{factory_id}/capacity · scope manufacturing:writeDeclare CapacityA declaration, born a DRAFT: nothing reaches the public until it is listed.
  • GET /manufacturing/{factory_id}/community · scope manufacturing:readFactory Community
  • POST /manufacturing/{factory_id}/community/circle · scope manufacturing:writeOpen Makers Circle
  • POST /manufacturing/{factory_id}/community/customers-room · scope manufacturing:writeOpen Customers RoomOne live room per product, pinned to a product of THIS factory.
  • POST /manufacturing/{factory_id}/community/exchange · scope manufacturing:writeOpen Engineers Exchange
  • GET /manufacturing/{factory_id}/cost-energy-estimates · scope manufacturing:readList Cost Energy Estimates
  • POST /manufacturing/{factory_id}/crews · scope manufacturing:writeCreate Crew
  • GET /manufacturing/{factory_id}/crews · scope manufacturing:readCrewsThe crews, their people, and the period's pay lines - what the payroll run would take.
  • GET /manufacturing/{factory_id}/digital-twins · scope manufacturing:readList Digital Twin Projects
  • PATCH /manufacturing/{factory_id}/directory · scope manufacturing:writeSet DirectoryThe dial. No second gate: being FOUND needs something LISTED, and listing is the go-live the factory already paid for (623) - a paywall on the dial would charge twice for one door.
  • GET /manufacturing/{factory_id}/documents · scope manufacturing:readList Documents
  • GET /manufacturing/{factory_id}/engineering-changes · scope manufacturing:readList Ecrs
  • GET /manufacturing/{factory_id}/floor · scope manufacturing:readFloorEvery work order with what it reserved and what it was inspected at, and the stock behind it - available computed, never stored.
  • GET /manufacturing/{factory_id}/forge · scope manufacturing:readForge For FactoryTHE DEMAND ORGAN, IMPORTED. The Inventor Forge lives in Marketing and stays there; this door reads it beside the factory that could answer it, and says which solutions were actually built.
  • POST /manufacturing/{factory_id}/forge/build · scope manufacturing:writeBuild A SolutionONE DOOR: a Forge solution becomes a product of this factory. The solution row keeps its origin and gains a pointer; the product names the friction point it answers, the way MF12's products name a need. Nothing is moved, copied or deleted in Marketing's organ.
  • POST /manufacturing/{factory_id}/forge/vassal · scope manufacturing:writeName The FactoryA vassal in the Forge's roster IS this factory. Naming it turns a list of names into a list of tenants somebody can actually ask for a quote.
  • GET /manufacturing/{factory_id}/geometry · scope manufacturing:readList Geometry
  • GET /manufacturing/{factory_id}/inventory · scope manufacturing:readList Inventory
  • GET /manufacturing/{factory_id}/inventory-locations · scope manufacturing:readList Inventory Locations
  • GET /manufacturing/{factory_id}/leads · scope manufacturing:readFactory Leads
  • GET /manufacturing/{factory_id}/licenses · scope manufacturing:readLicenses Desk
  • GET /manufacturing/{factory_id}/lots · scope manufacturing:readList Lots
  • POST /manufacturing/{factory_id}/lots/receive · scope manufacturing:writeReceive LotMaterial ARRIVED, with the supplier's own lot code on it. This is where a trace starts: without it, a recall can say which order a part went into but never where the part came from.
  • GET /manufacturing/{factory_id}/manufacturability-reviews · scope manufacturing:readList Manufacturability Reviews
  • GET /manufacturing/{factory_id}/members · scope manufacturing:readList Factory Members
  • POST /manufacturing/{factory_id}/members · scope manufacturing:writeAdd Factory Member
  • GET /manufacturing/{factory_id}/moat · scope manufacturing:readMoat
  • GET /manufacturing/{factory_id}/money · scope manufacturing:readFactory MoneyWhat this factory has taken on its own rail - read from the ONE fee ledger, never typed.
  • POST /manufacturing/{factory_id}/partner-orders · scope manufacturing:writeDraft OrderA purchase order on paper. The amount is the SUM OF THE LINES, never typed.
  • GET /manufacturing/{factory_id}/partner-orders · scope manufacturing:readList Orders
  • POST /manufacturing/{factory_id}/partner-rfqs · scope manufacturing:writeCreate RfqA quote request, born a DRAFT. Nothing leaves this platform when it is written.
  • GET /manufacturing/{factory_id}/partner-rfqs · scope manufacturing:readList Rfqs
  • POST /manufacturing/{factory_id}/partners · scope manufacturing:writeCreate PartnerA company outside Fitinty, written down by somebody who deals with them.
  • GET /manufacturing/{factory_id}/partners · scope manufacturing:readList PartnersThe partners this factory keeps, each with what it is certified for and what it has answered.
  • GET /manufacturing/{factory_id}/parts · scope manufacturing:readList Parts
  • GET /manufacturing/{factory_id}/production-orders · scope manufacturing:readList Production Orders
  • GET /manufacturing/{factory_id}/products · scope manufacturing:readList Products
  • GET /manufacturing/{factory_id}/prototypes · scope manufacturing:readList Prototype Projects
  • GET /manufacturing/{factory_id}/pulse · scope manufacturing:readRead PulseThe nights this factory has behind it, newest first - and what the trend of them says, which is only a trend once there are at least two.
  • GET /manufacturing/{factory_id}/quote-basis · scope manufacturing:readQuote BasisWHAT A LINE SHOULD COST, from this factory's own rows - MF2's computed model at this quantity, offered as the figure a price starts from. Never a price: the factory decides what it charges.
  • POST /manufacturing/{factory_id}/quotes · scope manufacturing:writeCreate QuoteThe factory's priced answer, born a DRAFT. The amount is the sum of the lines, never typed.
  • GET /manufacturing/{factory_id}/quotes · scope manufacturing:readList Quotes
  • GET /manufacturing/{factory_id}/recall-notices · scope manufacturing:readList Notices
  • GET /manufacturing/{factory_id}/roster · scope manufacturing:readRosterThe factory's people are the business's employees - `hris_employee`, never a factory copy.
  • GET /manufacturing/{factory_id}/routings · scope manufacturing:readList Routings
  • PATCH /manufacturing/{factory_id}/segment · scope manufacturing:writeSet SegmentWhat kind of maker this is. A KIT, never a fork - and the industry key the compliance register reads, so the duties a processor is shown differ from the ones an electronics shop is shown.
  • POST /manufacturing/{factory_id}/twin-render · scope manufacturing:writeRun Twin RenderTHE DOOR THAT REFUSES. It exists so the refusal is a sentence somebody reads rather than a missing feature they guess at - and so the day it opens, nothing has to be invented.
  • GET /manufacturing/{factory_id}/twin-render/readiness · scope manufacturing:readTwin Render ReadinessTWO GATES, BOTH NAMED. The render is metered and priced; it runs when the controlled test is recorded and he says go, and not one minute sooner. THE CALIBRATION IS READ ON THE SYSTEM CURSOR, and it must be: `render_calibration` is owner-only (one policy, `is_owner`), so a factory's own cursor reads ZERO runs through the wall and this door would answer "the gate is shut, 0 of 3 runs" on the day it…
  • GET /manufacturing/{factory_id}/work-centers · scope manufacturing:readList Work Centers
  • POST /manufacturing/{factory_id}/work-records · scope manufacturing:writeRecord WorkHours x an hourly rate, or quantity x a rate a piece. EARNED IS COMPUTED ONCE HERE and never re-derived; the rate is the member's own unless this record states a different one (a different task pays a different rate).
  • GET /needs · scope manufacturing:readList NeedsThe catalogue: open needs first, newest signal first. Every signed-in person reads it (an aggregate with no person in it - the registry's disclosure shape).
  • POST /needs · scope manufacturing:writeMint NeedA person writes a need (origin 'hand', in their name), or adopts a Forge friction point they can read (origin 'forge', linked to it - the demand organ imported, never copied).
  • GET /needs/signals · scope manufacturing:readList SignalsThe latest signals in the window: what is being asked for and how often, inside and outside.
  • PATCH /needs/{need_id}/status · scope manufacturing:writeSet Need StatusIts author or the Emperor moves it. A dismissal states why (422 without); 'answered' names what answered it in the reason when a person knows.
Logistics - 143 doors - logistics:read, logistics:write

Dispatch, routes and last-mile delivery.

  • GET /last-mile/drivers · scope logistics:readList Drivers
  • POST /last-mile/drivers · scope logistics:writeCreate Driver
  • PATCH /last-mile/drivers/{driver_id} · scope logistics:writeUpdate Driver
  • DELETE /last-mile/drivers/{driver_id} · scope logistics:writeDelete Driver
  • GET /last-mile/jobs · scope logistics:readList Jobs
  • POST /last-mile/jobs · scope logistics:writeCreate Job
  • GET /last-mile/jobs/{job_id} · scope logistics:readGet Job
  • DELETE /last-mile/jobs/{job_id} · scope logistics:writeDelete Job
  • POST /last-mile/jobs/{job_id}/assign · scope logistics:writeAssign Driver
  • POST /last-mile/jobs/{job_id}/cancel · scope logistics:writeCancel Job
  • POST /last-mile/jobs/{job_id}/deliver · scope logistics:writeMark Delivered
  • POST /last-mile/jobs/{job_id}/fail · scope logistics:writeMark Failed
  • POST /last-mile/jobs/{job_id}/in-transit · scope logistics:writeMark In Transit
  • POST /last-mile/jobs/{job_id}/pickup · scope logistics:writeMark Picked Up
  • GET /last-mile/my-organizations · scope logistics:readMy Organizations
  • GET /logistics · scope logistics:readList Operators
  • POST /logistics · scope logistics:writeCreate Operator
  • POST /logistics/adapters/{connection_id}/disconnect · scope logistics:writeDisconnectDISCONNECT ERASES. A flag on a kept secret is not a disconnection - and the row's CHECK refuses one.
  • POST /logistics/adapters/{connection_id}/read · scope logistics:writeRead Through DoorTHE TRANSPORT IS NOT A DOOR PARAMETER (MF13). The act is a plain function; the door only turns a shut lock into words.
  • POST /logistics/claims · scope logistics:writeCreate Claim
  • PATCH /logistics/claims/{claim_id}/status · scope logistics:writeSet Claim Status
  • POST /logistics/crew-members/{member_id}/leave · scope logistics:writeMember Leaves
  • POST /logistics/crews/{crew_id}/close · scope logistics:writeClose Crew
  • POST /logistics/crews/{crew_id}/members · scope logistics:writeAdd MemberA crew member IS an employee of the carrier's own business - the one roster, never a copy.
  • POST /logistics/deliveries/synthetic · scope logistics:writeCreate Delivery Job
  • PATCH /logistics/deliveries/{job_id}/status · scope logistics:writeSet Delivery Status
  • POST /logistics/fleets · scope logistics:writeCreate Fleet
  • POST /logistics/fuel-rebate-signals · scope logistics:writeCreate Fuel Rebate Signal
  • POST /logistics/gateways · scope logistics:writeRegister Gateway
  • POST /logistics/handoffs · scope logistics:writeCreate Handoff
  • POST /logistics/handoffs/ask · scope logistics:writeAsk
  • GET /logistics/handoffs/asked · scope logistics:readAskedThe origin's own asks. Read on the CALLER'S cursor through 656's origin arm; the carrier's name, the quote it SENT and the job's work word are the other side's walled rows, so they are read on the system cursor for EXACTLY the ids this caller's own rows name (591's law).
  • GET /logistics/handoffs/words · scope logistics:readWords
  • PATCH /logistics/handoffs/{handoff_id}/status · scope logistics:writeSet Handoff Status
  • POST /logistics/hubs · scope logistics:writeCreate Hub
  • GET /logistics/integrations · scope logistics:readList Integrations
  • POST /logistics/jobs/{job_id}/cancel · scope logistics:writeCancel
  • POST /logistics/jobs/{job_id}/could-not-deliver · scope logistics:writeCould Not Deliver
  • POST /logistics/jobs/{job_id}/deliver · scope logistics:writeDeliver
  • POST /logistics/jobs/{job_id}/driver · scope logistics:writeName The DriverA RECORD of who drives. Nobody is told; the driver sees the run on their own page. A licence that is expired, or that nobody has seen, is THE CARRIER'S DECISION (LG6's rule for an overdue vehicle) - and the record SAYS SO rather than deciding.
  • POST /logistics/jobs/{job_id}/pay · scope logistics:writePay JobThe shipper's pay door: a Checkout Session ON THE CARRIER'S OWN ACCOUNT, Fitinty's cut as the application fee from the one ladder, pending-before-Stripe. THE BROKERAGE LINE IS ASKED HERE, IN THE BODY, before anything is created. LG5b: the charge is a PAYMENT ROW of the kind the job is owed next (`plan_payment`, the one reading - deposit first where the quote asked one, the balance after, else the …
  • POST /logistics/jobs/{job_id}/repeat · scope logistics:writeRepeat Job"REPEAT THIS RUN": the shipper's standing yes, in the one definition's words, recorded on the row. The route is born on the SHIPPER'S cursor (WITH CHECK demands the buyer arm). Nothing is minted here - the next job appears when THIS one is delivered, and nothing is charged until they pay it.
  • POST /logistics/jobs/{job_id}/schedule · scope logistics:writeScheduleA day, and optionally one of the carrier's OWN vehicles. A record of a person's decision.
  • POST /logistics/jobs/{job_id}/start · scope logistics:writeStart
  • POST /logistics/jobs/{job_id}/stops · scope logistics:writeAdd Stop
  • POST /logistics/jobs/{job_id}/tip · scope logistics:writeTip JobTHE SHIPPER'S ACT ONLY: a gratuity on a PAID job, on the carrier's own account, with NO application fee (the row's own rule). The brokerage line is asked first - a tip moves money on the same account.
  • POST /logistics/lane-contracts/{contract_id}/agree · scope logistics:writeAgree Lane ContractTHE SHIPPER'S HALF of the two recorded yeses. The row names them, and the body asks again.
  • POST /logistics/lane-contracts/{contract_id}/end · scope logistics:writeEnd Lane ContractEither side ends it with a reason; the row stays as history and a new one may follow.
  • POST /logistics/lanes/{lane_id}/active · scope logistics:writeSet Lane Active
  • PATCH /logistics/leads/{lead_id} · scope logistics:writeSet Lead Statusengaged starts the response clock ONCE (first_contact_at); closed needs a reason. LG4's moat measures from these words, so a status is a fact the carrier states, never a guess.
  • POST /logistics/loads/synthetic · scope logistics:writeCreate Synthetic Load
  • PATCH /logistics/loads/{load_id}/assign · scope logistics:writeAssign LoadProposes a synthetic dispatch assignment - a human-reviewed proposal, never a real reservation or brokerage commitment (spec section 14).
  • PATCH /logistics/loads/{load_id}/status · scope logistics:writeSet Load Status
  • POST /logistics/ltl-groups · scope logistics:writeCreate Ltl Group
  • GET /logistics/mine · scope logistics:readGet My Operator Endpoint
  • GET /logistics/my/asks · scope logistics:readMy Asks DoorA person's own asks across carriers - ownership decided in code from the account email, per row (the ONE ownership rule); the lead table itself stays the carrier's alone.
  • GET /logistics/my/jobs · scope logistics:readMy Jobs
  • GET /logistics/my/money · scope logistics:readMy MoneyTHE SHIPPER'S OWN: what each job is owed next (the ONE reading), the payments made, the routes they started and the contracts proposed to them - the carriers named on the system cursor for exactly those rows, since the shipper's cursor reads nothing of a carrier.
  • GET /logistics/my/runs · scope logistics:readMy RunsWhat is recorded ABOUT THIS PERSON, read on THEIR OWN cursor through 659's `_own` arms: their crews, their pay lines as written, their qualification file, their hours-of-service days. Their RUNS - the jobs that name them - are the carrier's walled rows, so those are read on the system cursor for EXACTLY this person's own employee ids, from a whitelist (no price, no shipper, no ask).
  • POST /logistics/my/runs/{job_id}/deliver · scope logistics:writeDeliver My RunTHE ONE THING A DRIVER WRITES. The job is the carrier's walled row and the driver has no seat at the desk, so the wall is NOT widened: the body asks, on the driver's OWN cursor, which roster rows are theirs; the job is then read on the system cursor and must NAME one of them; and the delivery is written by LG6's ONE delivery writer, with the driver as the actor on the chain and in the trail.
  • POST /logistics/offers/{offer_id}/withdraw · scope logistics:writeWithdraw Offer
  • POST /logistics/payments/webhook · scope logistics:writeLogistics Stripe WebhookThe pillar's own verified door, the Services shape exactly: signature or refusal, captured events only, idempotent by the pending->paid guard + the fee ledger's unique payment id.
  • GET /logistics/pulse/readiness · scope logistics:readPulse ReadinessWHETHER THERE IS ANYTHING TO SELL YET. Counts only - the platform's own cursor, because no carrier may count another's nights.
  • POST /logistics/qualifications/{qualification_id}/seen · scope logistics:writeSaw The DocumentThe caller says THEY saw the paper, today. It is their name on the record - nobody is named for them.
  • POST /logistics/quotes/accept/{token} · scope logistics:writeAccept QuoteSIGNED-IN acceptance: the job is born on the ACCEPTOR'S OWN CURSOR (logistics_job's WITH CHECK demands buyer_user_id = the caller); the quote and the ask then flip on the system cursor.
  • POST /logistics/quotes/{quote_id}/send · scope logistics:writeSend QuoteSENT, and the accept link handed back - sending it stays the carrier's hand. The ask is engaged: the carrier answered, and the moat's response clock stops on this stamp.
  • POST /logistics/quotes/{quote_id}/withdraw · scope logistics:writeWithdraw Quote
  • POST /logistics/reliability-scores · scope logistics:writeCreate Reliability Score
  • PATCH /logistics/reliability-scores/{score_id}/appeal · scope logistics:writeAppeal Reliability Score
  • POST /logistics/repair-events · scope logistics:writeCreate Repair Event
  • POST /logistics/repair-events/{event_id}/ask-a-shop · scope logistics:writeAsk A Repair Shop
  • PATCH /logistics/repair-events/{event_id}/status · scope logistics:writeSet Repair Event Status
  • POST /logistics/routes/preview · scope logistics:writeCreate Route PreviewComputes a real deadhead percentage from the operator's own real loaded/empty mileage inputs - no fabricated route geometry or distance, since no real mapping engine (OpenStreetMap/Valhalla) is wired in yet.
  • POST /logistics/routes/{route_id}/stop · scope logistics:writeStop RouteEither side, with a reason. The row's own rule: a route that is off says who and why.
  • GET /logistics/segments/kits · scope logistics:readSegments And Kits
  • POST /logistics/services · scope logistics:writeCreate Service
  • PATCH /logistics/services/{service_id} · scope logistics:writeEdit ServiceThe words of a service - a kit's draft is a starting point, never the carrier's own sentence.
  • PATCH /logistics/services/{service_id}/status · scope logistics:writeSet Service Status'offered' IS THE GO-LIVE: the first moment a stranger can find this carrier. The one gate is asked HERE, in the body, before the write - a Depends misses a plain-Python caller.
  • POST /logistics/stops/{stop_id}/outcome · scope logistics:writeStop Outcome
  • POST /logistics/tenders/{tender_id}/approve · scope logistics:writeApprove Tender
  • POST /logistics/tenders/{tender_id}/transmit · scope logistics:writeTransmit Tender Door
  • POST /logistics/tenders/{tender_id}/withdraw · scope logistics:writeWithdraw Tender
  • POST /logistics/vehicle-documents/{document_id}/seen · scope logistics:writeSaw The Paper
  • POST /logistics/vehicles · scope logistics:writeCreate Vehicle
  • PATCH /logistics/vehicles/{vehicle_id}/health · scope logistics:writeUpdate Vehicle Health
  • PATCH /logistics/vehicles/{vehicle_id}/maintenance · scope logistics:writeUpdate Vehicle Maintenance
  • PATCH /logistics/vehicles/{vehicle_id}/service-interval · scope logistics:writeSet Service Interval
  • POST /logistics/vehicles/{vehicle_id}/services · scope logistics:writeRecord Service
  • POST /logistics/vehicles/{vehicle_id}/telemetry/synthetic · scope logistics:writeCapture Synthetic Telemetry
  • DELETE /logistics/windows/{window_id} · scope logistics:writeDelete Window
  • POST /logistics/work-records/{record_id}/void · scope logistics:writeVoid Work
  • GET /logistics/{operator_id} · scope logistics:readGet Operator
  • PATCH /logistics/{operator_id} · scope logistics:writeUpdate Operator
  • POST /logistics/{operator_id}/adapters · scope logistics:writeConnect
  • GET /logistics/{operator_id}/adapters · scope logistics:readAdapters
  • GET /logistics/{operator_id}/capacity · scope logistics:readCapacityThe Calendar desk's reading: the windows, the next fortnight's open times day by day, the lanes and the offers - and, in words, what silenced a window and what this reading cannot know.
  • GET /logistics/{operator_id}/claims · scope logistics:readList Claims
  • GET /logistics/{operator_id}/community · scope logistics:readCarrier Community
  • POST /logistics/{operator_id}/community/drivers-circle · scope logistics:writeOpen Drivers Circle
  • POST /logistics/{operator_id}/community/operators-exchange · scope logistics:writeOpen Operators Exchange
  • POST /logistics/{operator_id}/community/shippers-room · scope logistics:writeOpen Shippers RoomOne live shippers' room per carrier - the one writer refuses a second, in words.
  • POST /logistics/{operator_id}/crews · scope logistics:writeCreate Crew
  • GET /logistics/{operator_id}/crews · scope logistics:readCrews
  • GET /logistics/{operator_id}/deliveries · scope logistics:readList Deliveries
  • PATCH /logistics/{operator_id}/directory · scope logistics:writeSet DirectoryThe dial. No second gate: being FOUND needs something OFFERED, and offering is the go-live the carrier already paid for - a paywall on the dial would charge twice for one door.
  • GET /logistics/{operator_id}/fleet-truth · scope logistics:readFleet Truth
  • GET /logistics/{operator_id}/fleets · scope logistics:readList Fleets
  • GET /logistics/{operator_id}/fuel-rebate-signals · scope logistics:readList Fuel Rebate Signals
  • GET /logistics/{operator_id}/gateways · scope logistics:readList Gateways
  • GET /logistics/{operator_id}/handoffs · scope logistics:readList Handoffs
  • POST /logistics/{operator_id}/hours-of-service · scope logistics:writeRecord Hours Of Service
  • PATCH /logistics/{operator_id}/hours-of-service/limits · scope logistics:writeSet Limits
  • GET /logistics/{operator_id}/hubs · scope logistics:readList Hubs
  • POST /logistics/{operator_id}/lane-contracts · scope logistics:writePropose Lane ContractTHE CARRIER'S HALF: a standing price on one of ITS OWN lanes for the shipper a job of theirs names, priced in LINES through LG5's one line arithmetic, in the one definition's words. Proposed, not agreed.
  • POST /logistics/{operator_id}/lanes · scope logistics:writeAdd Lane
  • GET /logistics/{operator_id}/leads · scope logistics:readOperator Leads
  • POST /logistics/{operator_id}/leads · scope logistics:writeWrite Down A Lead
  • GET /logistics/{operator_id}/loads · scope logistics:readList Loads
  • GET /logistics/{operator_id}/ltl-groups · scope logistics:readList Ltl Groups
  • GET /logistics/{operator_id}/members · scope logistics:readList Operator Members
  • POST /logistics/{operator_id}/members · scope logistics:writeAdd Operator Member
  • GET /logistics/{operator_id}/moat · scope logistics:readMoat
  • GET /logistics/{operator_id}/money · scope logistics:readMoneyThe Jobs desk's reading: every payment by kind, the routes standing, the contracts by lane - and the lanes and shippers the propose form offers (a desk that shows something must read it).
  • POST /logistics/{operator_id}/offers · scope logistics:writeMint Offer DoorThe hand's offer: an ask, and the next three REAL open times - no later than the day the shipper needs it.
  • GET /logistics/{operator_id}/pulse · scope logistics:readRead PulseThe nights this carrier has behind it, newest first - and what moved, which is only a trend once there are two.
  • POST /logistics/{operator_id}/qualifications · scope logistics:writeAdd Qualification
  • POST /logistics/{operator_id}/quotes · scope logistics:writeCreate QuoteThe carrier's priced answer, born a DRAFT. The amount is the sum of the lines, never typed.
  • GET /logistics/{operator_id}/quotes · scope logistics:readList Quotes
  • GET /logistics/{operator_id}/reliability-scores · scope logistics:readList Reliability Scores
  • GET /logistics/{operator_id}/repair-events · scope logistics:readList Repair Events
  • GET /logistics/{operator_id}/roster · scope logistics:readRoster
  • GET /logistics/{operator_id}/route-previews · scope logistics:readList Route Previews
  • PATCH /logistics/{operator_id}/segment · scope logistics:writeSet Segment
  • GET /logistics/{operator_id}/services · scope logistics:readList Services
  • POST /logistics/{operator_id}/services/from-kit · scope logistics:writePropose KitThe segment's starter kit, minted as DRAFTS. It offers nothing and never runs twice into doubles.
  • GET /logistics/{operator_id}/standing · scope logistics:readStandingThe carrier reads ITS OWN standing as a shipper would: the same function the public page calls, so the desk can never show a mark the page does not. The wall is asked FIRST on the caller's own cursor (a stranger reads no operator); the signals are then read on the system cursor, because the fee ledger is not a member's to read.
  • GET /logistics/{operator_id}/telemetry · scope logistics:readList Telemetry
  • POST /logistics/{operator_id}/tenders · scope logistics:writeDraft Tender
  • POST /logistics/{operator_id}/vehicle-documents · scope logistics:writeAdd Document
  • GET /logistics/{operator_id}/vehicles · scope logistics:readList Vehicles
  • POST /logistics/{operator_id}/windows · scope logistics:writeAdd Window
  • GET /logistics/{operator_id}/work · scope logistics:readWork
  • POST /logistics/{operator_id}/work-records · scope logistics:writeRecord WorkA measure x a rate. EARNED IS COMPUTED ONCE HERE. The rate is the member's own for that kind unless this record states another (a different run pays a different rate).
Governor Suite - 80 doors - governor-suite:read, governor-suite:write

The governor's own instruments: documents, draws, reimbursement and status.

  • POST /governor-suite · scope governor-suite:writeCreate Profile
  • POST /governor-suite/benefits · scope governor-suite:writeCreate Benefit Plan
  • PATCH /governor-suite/benefits/{plan_id}/toggle-enrollment · scope governor-suite:writeToggle Benefit Enrollment
  • POST /governor-suite/compliance-clerk/tasks · scope governor-suite:writeCreate Clerk Task
  • PATCH /governor-suite/compliance-clerk/tasks/{task_id}/status · scope governor-suite:writeSet Clerk Task Status
  • POST /governor-suite/cpa-export · scope governor-suite:writeCreate Cpa Export State
  • PATCH /governor-suite/cpa-export/{state_id}/status · scope governor-suite:writeSet Cpa Export Status
  • POST /governor-suite/credentials/{credential_id}/seen · scope governor-suite:writeRecord Seeing ItTHE CALLER'S OWN NAME GOES ON IT. Somebody recording that they saw a document is the only thing that moves a credential out of 'nobody has recorded seeing the document'.
  • POST /governor-suite/credentials/{credential_id}/superseded · scope governor-suite:writeSupersedeA RENEWAL REPLACES, IT DOES NOT OVERWRITE. The register keeps what was true, which is the whole point of holding it: 'we held that licence until March' is a different fact from 'we hold it'.
  • POST /governor-suite/document-templates · scope governor-suite:writeCreate Document Template
  • PATCH /governor-suite/document-templates/{template_id}/white-label · scope governor-suite:writeToggle White LabelNo pricing or branding rule changes without approval (spec section 18) - this endpoint itself is the explicit approval action; premium entitlement verification stays a real follow-on (not modeled in this Core pass, matching the spec's own unresolved pricing-tier boundary in section 24).
  • POST /governor-suite/draws/propose · scope governor-suite:writePropose Draw
  • PATCH /governor-suite/draws/{draw_id}/status · scope governor-suite:writeSet Draw Status
  • POST /governor-suite/filing-calendar · scope governor-suite:writeCreate Filing Calendar Item
  • PATCH /governor-suite/filing-calendar/{item_id}/status · scope governor-suite:writeSet Filing Calendar Status
  • POST /governor-suite/filing-scan · scope governor-suite:writeRun ScanRead what the scout has already stored and propose. It fetches nothing.
  • POST /governor-suite/filing-sources · scope governor-suite:writeLink SourceSay that a page the platform already watches belongs to one government. It adds no source - the page is registered on the scout registry first, where its terms, its robots answer and its calls-a-day dial live.
  • POST /governor-suite/filings/{filing_id}/approve · scope governor-suite:writeApprove FilingA PERSON'S WORD, on a PARTICULAR package. Approving and then filing something else is the defect the hash exists to make impossible, so an approval carries the hash it approved.
  • POST /governor-suite/filings/{filing_id}/transmit · scope governor-suite:writeTransmit Door
  • POST /governor-suite/filings/{filing_id}/withdraw · scope governor-suite:writeWithdraw Filing
  • POST /governor-suite/gate-strikes · scope governor-suite:writeRecord Gate Strike
  • GET /governor-suite/governments · scope governor-suite:readList GovernmentsThe picker. A DESK THAT SHOWS SOMETHING MUST READ IT - a formation jurisdiction chosen from an empty list is a tenant who cannot record where their company lives, which is the same defect four waves of this platform have already shipped.
  • GET /governor-suite/integrations · scope governor-suite:readList Integrations
  • POST /governor-suite/llc-renewals · scope governor-suite:writeCreate Llc Renewal
  • PATCH /governor-suite/llc-renewals/{renewal_id}/status · scope governor-suite:writeSet Llc Renewal Status
  • POST /governor-suite/member-resolutions/generate · scope governor-suite:writeGenerate Member Resolution
  • GET /governor-suite/mine · scope governor-suite:readGet My Profile Endpoint
  • POST /governor-suite/officers/{officer_id}/left-office · scope governor-suite:writeLeft OfficeLEAVING IS A DATE, NOT A DELETE. The row stays, because what was true stays true.
  • POST /governor-suite/overrides/{override_id}/withdraw · scope governor-suite:writeWithdraw OverrideWITHDRAWN, NEVER DELETED, and it takes its own reason - because "we said expired, you said current, then you withdrew that" is a history somebody will need to read.
  • POST /governor-suite/registered-agents/{engagement_id}/end · scope governor-suite:writeEnd AgentENDING IS A DATE AND A REASON, NOT A DELETE (GV5's law): who served as your agent last year is a question somebody will ask, and a state that has been served at that address will ask it.
  • POST /governor-suite/registered-agents/{engagement_id}/engage · scope governor-suite:writeEngage Door
  • POST /governor-suite/reimbursements · scope governor-suite:writeCreate Reimbursement
  • GET /governor-suite/reimbursements/{req_id}/receipt · scope governor-suite:readDownload Receipt
  • PATCH /governor-suite/reimbursements/{req_id}/status · scope governor-suite:writeSet Reimbursement Status
  • GET /governor-suite/renewal-rules · scope governor-suite:readList RulesWorld-readable: a tenant may see the rule its own dates are worked out from, which is the whole difference between a date and a date somebody can check.
  • POST /governor-suite/renewal-rules · scope governor-suite:writeRecord RuleTHE EMPEROR'S HAND. A rule is platform data read by every tenant, so it is not a tenant's to write - the same posture as the government catalogue it hangs from. GV7's scout may one day propose these from a government's own published page; adopting one will still be a person's act.
  • GET /governor-suite/renewal-rules/needed · scope governor-suite:readRules Worth RecordingHIS RULING (2026-09-21): "seed only the states tenants use". This is the list that makes that ruling actionable instead of aspirational - the governments REAL businesses are really formed in or really hold credentials from, counted, newest need first, so research goes where it is used rather than across fifty states nobody on this platform touches. OWNER-ONLY AND AGGREGATE. It counts across every…
  • POST /governor-suite/reports/annual · scope governor-suite:writeCreate Annual Pnl
  • PATCH /governor-suite/reports/annual/{package_id}/status · scope governor-suite:writeSet Annual Pnl Status
  • GET /governor-suite/rule-proposals · scope governor-suite:readList ProposalsWhat the outside pages said, waiting for a person. Owner-only: the scout registry is his, and what a tenant reads is the RULE that comes out of this, with its source written on it.
  • POST /governor-suite/rule-proposals/{proposal_id}/adopt · scope governor-suite:writeAdoptADOPTING IS A PERSON'S ACT, AND IT GOES THROUGH GV3'S OWN DOOR. This writes no rule itself - it hands `record_rule` the proposal's figures and, as its source, the words that were read and the link they were read at, so every tenant reading the rule can check the government's own sentence. A proposal with only a cadence is refused: GV3 has no way to say "every two years, starting nobody knows when"…
  • POST /governor-suite/rule-proposals/{proposal_id}/dismiss · scope governor-suite:writeDismissA DISMISSAL SAYS WHY, and the row refuses one without it. Without a reason the same title is proposed and ignored every month, and the pile stops meaning anything - CO1's own hand rule.
  • POST /governor-suite/tasks · scope governor-suite:writeCreate Task
  • PATCH /governor-suite/tasks/{task_id}/status · scope governor-suite:writeSet Task Status
  • POST /governor-suite/w9-w8 · scope governor-suite:writeCreate W9 W8
  • PATCH /governor-suite/w9-w8/{req_id}/mark-on-file · scope governor-suite:writeMark W9 W8 On File
  • GET /governor-suite/{profile_id} · scope governor-suite:readGet Profile
  • GET /governor-suite/{profile_id}/benefits · scope governor-suite:readList Benefit Plans
  • GET /governor-suite/{profile_id}/compliance-clerk · scope governor-suite:readList Clerk Tasks
  • GET /governor-suite/{profile_id}/cpa-export · scope governor-suite:readList Cpa Export States
  • GET /governor-suite/{profile_id}/credentials · scope governor-suite:readList Credentials
  • POST /governor-suite/{profile_id}/credentials · scope governor-suite:writeAdd Credential
  • GET /governor-suite/{profile_id}/document-templates · scope governor-suite:readList Document Templates
  • GET /governor-suite/{profile_id}/draws · scope governor-suite:readList Draw Proposals
  • GET /governor-suite/{profile_id}/entity · scope governor-suite:readGet Entity
  • PUT /governor-suite/{profile_id}/entity · scope governor-suite:writeSet EntityOne row per business, so this is an upsert and never a second entity. Changing where a company was FORMED is not an edit anybody makes twice by accident, and the audit trail keeps both.
  • GET /governor-suite/{profile_id}/filing-calendar · scope governor-suite:readList Filing Calendar
  • POST /governor-suite/{profile_id}/filings · scope governor-suite:writePrepare FilingPREPARING IS FREE AND ALWAYS HAS BEEN. The lock is on what leaves, not on what a business may look at - a package a person can read is the whole value of the wave while the rail is dark.
  • GET /governor-suite/{profile_id}/gate-strikes · scope governor-suite:readList Gate Strikes
  • GET /governor-suite/{profile_id}/llc-renewals · scope governor-suite:readList Llc Renewals
  • GET /governor-suite/{profile_id}/member-resolutions · scope governor-suite:readList Member Resolutions
  • GET /governor-suite/{profile_id}/members · scope governor-suite:readList Members
  • POST /governor-suite/{profile_id}/members · scope governor-suite:writeAdd Member
  • GET /governor-suite/{profile_id}/officers · scope governor-suite:readList Officers
  • POST /governor-suite/{profile_id}/officers · scope governor-suite:writeRecord Officer
  • GET /governor-suite/{profile_id}/overrides · scope governor-suite:readList OverridesThe history, because that is the point of never deleting one.
  • POST /governor-suite/{profile_id}/overrides · scope governor-suite:writeRecord OverrideA PERSON DISAGREES, IN THEIR OWN WORDS, WITH ONE READING.
  • GET /governor-suite/{profile_id}/payout-status · scope governor-suite:readGet Payout Status
  • PATCH /governor-suite/{profile_id}/payout-status · scope governor-suite:writeUpdate Payout Status
  • GET /governor-suite/{profile_id}/registered-agents · scope governor-suite:readList Agents
  • POST /governor-suite/{profile_id}/registered-agents · scope governor-suite:writeRecord AgentRECORDING AN AGENT YOU ALREADY HAVE IS FREE, and it is what most businesses need: the register should hold that fact rather than only the ones this platform arranged.
  • GET /governor-suite/{profile_id}/reimbursements · scope governor-suite:readList Reimbursements
  • GET /governor-suite/{profile_id}/renewal-outlook · scope governor-suite:readGet Renewal Outlook
  • GET /governor-suite/{profile_id}/reports/annual · scope governor-suite:readList Annual Pnl
  • GET /governor-suite/{profile_id}/tasks · scope governor-suite:readList Tasks
  • GET /governor-suite/{profile_id}/tax-sanctuary · scope governor-suite:readGet Tax Sanctuary
  • POST /governor-suite/{profile_id}/tax-sanctuary/sync · scope governor-suite:writeSync Tax SanctuaryReal cross-pillar sync: reads the tenant's own actual linked Tax profile (same organization_id) - never fabricated. If no Tax profile exists yet for this organization, this honestly reports tax_profile_status='not_linked' rather than inventing data.
  • GET /governor-suite/{profile_id}/treasury · scope governor-suite:readGet Treasury Summary
  • POST /governor-suite/{profile_id}/treasury/sync · scope governor-suite:writeSync Treasury SummaryReal cross-pillar sync: reads the tenant's own actual linked Treasury profile (same organization_id) - every total here is genuinely computed from real Treasury rows, never fabricated. Honestly reports treasury_linked=false when no Treasury profile exists yet.
  • GET /governor-suite/{profile_id}/w9-w8 · scope governor-suite:readList W9 W8

Public doors (no key needed)

200 doors
  • GET /public/affiliate/go/{offer_id}Go
  • POST /public/billing/dodo/webhookDodo WebhookDodo's subscription lifecycle callback. RETURNS 200 FOR EVENTS WE DO NOT CARE ABOUT. Dodo retries anything that is not a 200 with exponential backoff, so answering 4xx to an irrelevant event buys retries for nothing.
  • POST /public/booking/cancel/{token}Public CancelA guest cancels with the token from their confirmation. No account, exactly as they booked.
  • GET /public/booking/{slug}Public Slots
  • POST /public/booking/{slug}/bookPublic Book
  • GET /public/cinema/access/{access_token}Check Access
  • GET /public/cinema/license/{token}License Offer
  • GET /public/cinema/license/{token}/downloadDownload Licensed FilmThe delivery: the file moves only for CAPTURED money.
  • POST /public/cinema/license/{token}/payPay License
  • POST /public/cinema/releases/{release_id}/buyBuy ScreeningA viewer buys access to watch. No account needed (the guest-checkout ruling); the purchase is born pending BEFORE Stripe, on the STUDIO'S own account.
  • GET /public/cinema/releases/{release_id}/watchWatch ReleaseTHE WALL. A published free release streams to anyone; a priced one streams only for a token carrying captured money for THIS release - checked in code, because RLS scoping a row does not authorize its contents.
  • GET /public/cinema/site/{slug}/filmsPublic FilmsWhat a stranger with the studio's link sees: published releases only, price stated or free stated - the drafts stay invisible.
  • POST /public/concierge/avatar-clipAvatar ClipA lip-synced mp4 of her speaking a server-owned line (greeting or stored reply). Returns the clip when cached; otherwise kicks a background render and answers 202 {"status": "rendering"} - the widget polls and swaps the clip in when ready (the tier design: text instant, cloned voice ~10s, true lip-sync swapped in when rendered).
  • POST /public/concierge/education-intentEducation Intent
  • POST /public/concierge/sessionStart Session
  • POST /public/concierge/speakSpeakSpeaks a server-owned line (greeting or stored reply) - never caller-supplied text (the counsel's guard). Voice tiering: her ONE cloned voice (VoxCPM2, 27 locales - one consistent female voice, and the only voice for 14 previously-silent languages), else Piper where it exists (uk), else 404 = honest text-only. First clone per line blocks ~10-50s; cached mp3 forever after. NOTE the locale is used i…
  • GET /public/concierge/{visitor_token}/messagesList MessagesA visitor reading their own conversation back. THIS KEPT THE WRONG END. `ORDER BY created_at LIMIT 200` ascending takes the OLDEST two hundred, so past message 200 the widget stopped showing what had just been said - the visitor typed, a reply was written, and neither appeared. A truncated list is a lie about being whole; this was a chat that quietly stopped working, and the silent cap is what hi…
  • POST /public/concierge/{visitor_token}/messagesSend Message
  • POST /public/concierge/{visitor_token}/voiceVoice MessageTalk to the Concierge: audio in -> Whisper transcribes (language-hinted) -> the SAME grounded brain replies. Returns the concierge message (with id) so the widget can play it back in voice. The visitor's spoken words are stored as their turn.
  • POST /public/confirm/{token}Confirm SubscriptionDouble opt-in's second half. Confirming is benign (it grants nothing but the list membership the person just asked for), so the mailed link may act on GET - the industry's own shape for confirmation links.
  • GET /public/confirm/{token}Confirm SubscriptionDouble opt-in's second half. Confirming is benign (it grants nothing but the list membership the person just asked for), so the mailed link may act on GET - the industry's own shape for confirmation links.
  • GET /public/congregation/campaign/{fund_slug}Public CampaignThe outward-facing campaign: the fund, its goal, honest progress, and the door to the church's own giving page with this fund preselected.
  • POST /public/congregation/{site_slug}/care-requestsSubmit Care RequestAnyone may ask for prayer or care - no account, the requester chooses what the congregation may see. Throttled like every anonymous door.
  • GET /public/congregation/{site_slug}/prayerPublic Prayer PageWhat the site's prayer page needs to stand: the congregation's name and its own words (502's labels - a mosque's page says imam, a creator's says supporters). A business with no congregation has NO prayer page: a door must lead to someone who will read what is written. Nothing a person ever asked is readable here - care requests have no public read arm, ever. Registered AFTER /campaign/{fund_slug…
  • POST /public/csp-reportReceive Csp ReportThe report endpoint the Caddy block names. Answers 204 with nothing - a browser sends these in the background and reads no answer. Throttled per address (a page with a broken policy sends one per blocked resource, a script sends millions).
  • GET /public/developers/referenceReferenceThe reference: families of doors a key may open (with each door's own words), the public doors, how a key is presented, how a delivery is signed, which families a webhook may name.
  • GET /public/education/catalogPublic Catalog
  • GET /public/education/credentials/{kind}/{certificate_id}Verify Credential Public
  • GET /public/education/transcripts/{share_id}Verify Transcript Public
  • GET /public/employment/{verification_id}/verifyVerify Employment
  • POST /public/events/webhookEvents Stripe WebhookThe pillar-webhook pattern: own secret, signature or refusal, captured only.
  • GET /public/events/{slug}Public Event
  • POST /public/events/{slug}/buy-ticketBuy TicketThe paid door: pending purchase FIRST, Checkout on the tenant's own account, the seat granted only when the webhook sees captured money.
  • POST /public/events/{slug}/registerPublic RegisterFREE events register directly - the ticket code IS the confirmation. A priced event refuses this door and points at the paid one, so nobody holds a seat they never paid for.
  • GET /public/farms/directoryPublic Directory
  • POST /public/farms/directory/{farm_id}/askPublic Ask
  • GET /public/farms/offers/{token}Read Offer
  • POST /public/farms/offers/{token}/acceptAccept OfferAccepting names ONE of the offered times; the lead is engaged (the farm answered and the buyer said yes); the sale still happens on the farm's store, where the link points.
  • GET /public/farms/quotes/{token}Read Quote
  • POST /public/farms/region-asksPublic Region AskThe directory's 'ask the farms near me' - no account needed; an account holder is joined by email.
  • GET /public/farms/showcase/{asset_id}Public Showcase FileA PUBLISHED showcase asset's file, for the farm's page - a draft stays the staff's (404).
  • GET /public/farms/site/{slug}Public Farm Page
  • POST /public/farms/site/{slug}/joinPublic Farm JoinThe page's "keep me posted": the email joins the org's ONE audience list (G2's table, the practice door's shape) with the consent words recorded and source 'farm' - the CHECK widened WITH this caller (606). Consent is a real yes or a 422.
  • GET /public/forms/{slug}Public Form
  • POST /public/forms/{slug}/submitPublic Submit
  • GET /public/games/playtest/{token}Playtest OfferWhat the invited tester reads: the world, who asked, how long the door is open. Facts only, on the system cursor - the invite IS the credential.
  • POST /public/games/worlds/{world_id}/wishlistJoin WishlistThe demo gate's second door: not ready to buy, but wants to hear. The email joins the org's ONE audience list (consent words recorded there) and the wishlist row remembers which world - both on the system cursor, fenced to worlds whose demo the tenant deliberately opened.
  • GET /public/give/moncash/returnMoncash ReturnWhere MonCash sends a payer back to. PUBLIC, and treated as hostile. THE QUERY PARAMETERS PROVE NOTHING. Anyone can visit this URL with any order id - it is a plain GET in a browser. What decides whether money moved is `RetrieveOrderPayment`, asked of MonCash with the church's own credential, and the AMOUNT RECORDED IS THE ONE MONCASH REPORTS. Trusting an amount from the URL would let a donor ret…
  • POST /public/give/paystack/webhookPaystack WebhookPaystack's completion callback. A SEPARATE ENDPOINT FROM STRIPE'S, because the two sign differently: Stripe uses HMAC SHA256 with a per-endpoint signing secret, Paystack uses HMAC SHA512 with the API secret key itself. One endpoint accepting both would have to guess which scheme applies, and guessing wrong on a signature check is how an unverified event gets processed. RETURNS 200 EVEN WHEN THE …
  • POST /public/give/webhookGive WebhookStripe's completion callback for gifts made on a connected account. SEPARATE FROM THE ACCOUNT WEBHOOK because these arrive on the CONNECT endpoint with their own signing secret, and mixing them means one bad secret silently disables both.
  • GET /public/give/{slug}Public PageWhat a stranger with the link sees. No session, on purpose. REFUSES TO SHOW A PAGE THAT CANNOT TAKE MONEY. If the church has not finished onboarding, a donor who fills in an amount and hits a Stripe error blames the church. Better to say plainly that giving is not open yet.
  • POST /public/give/{slug}/checkoutStart GiftCreate a Checkout Session ON THE CHURCH'S ACCOUNT and hand back the URL. NO CARD DETAILS TOUCH THIS PLATFORM. Stripe hosts the payment page; we send an amount and a destination and receive a link. That is not only a PCI position, it is the same principle as the Express onboarding: the regulated part belongs to the party equipped to carry it.
  • GET /public/interpret/{code}Public Session
  • GET /public/interpret/{code}/turnsPublic Turns
  • POST /public/interpret/{code}/turnsPublic SpeakOne push-to-talk turn: hear it, translate it, speak it, append it. Synchronous by design - the speaker is standing there waiting; a few seconds is the product.
  • GET /public/interpret/{code}/turns/{turn_id}/audioPublic Turn Audio
  • GET /public/l/{code}Resolve Short LinkThe public resolver: returns the target for the web layer's 302 and counts the click as a daily aggregate - nothing about the visitor is stored, ever.
  • GET /public/logistics/ask-wordsPublic Ask WordsThe words an ask is made under and the pickers it offers - from the ONE definition, so a page can never show one promise while the row keeps another.
  • POST /public/logistics/carrier/{operator_id}/askPublic Ask
  • GET /public/logistics/carrier/{slug}Public Carrier Page
  • POST /public/logistics/carrier/{slug}/joinPublic Carrier JoinTHE CARRIER'S "KEEP ME POSTED" - shipped WITH the page this time (MF4a shipped without one and a factory's digest had a reach of zero until MF19). One audience list, the consent words RECORDED, a real yes or a 422, and the source word widened with this caller (647).
  • GET /public/logistics/directoryPublic Directory
  • GET /public/logistics/offers/{token}Read Offer
  • POST /public/logistics/offers/{token}/acceptAccept OfferAccepting names ONE of the offered times. The time is checked STILL OPEN at this moment - two offers may have held the same hour, and the second to accept it is told so rather than double-booked.
  • GET /public/logistics/quotes/{token}Read Quote
  • GET /public/logistics/track/{token}TrackBY ITS TOKEN, ON THE SYSTEM CURSOR - and built from two WHITELISTS, so nothing the carrier wrote about a person or a place can reach a stranger by being added to a row later.
  • POST /public/manufacturing/capacity/{listing_id}/askPublic Capacity Ask
  • GET /public/manufacturing/directoryPublic Directory
  • POST /public/manufacturing/factory/{factory_id}/askPublic Ask
  • GET /public/manufacturing/factory/{slug}Public Factory Page
  • POST /public/manufacturing/factory/{slug}/joinPublic Factory JoinMF19 - THE FACTORY'S "KEEP ME POSTED", which this pillar never had. FM6 gave the farm page this door and OF8 gave the practice page one; MF4a built the public factory page without it, so there was NO WAY for a visitor to join a factory's audience. That is not a missing convenience: the community digest may only reach somebody who has already said yes to hearing from this business, so a factory's …
  • GET /public/manufacturing/license/{token}Read License
  • GET /public/manufacturing/license/{token}/downloadDownload Licensed FileThe delivery: the file moves only for CAPTURED money - 402 before, an attachment after.
  • POST /public/manufacturing/license/{token}/payPay LicenseThe pay door in the shape's place. It REFUSES: no price stands on any licence, and none can until a ruling sets one - said in words, never a provider error.
  • GET /public/manufacturing/quotes/{token}Read Quote
  • POST /public/manufacturing/region-asksOpen Region Ask PublicA buyer with no account asks the region. Their email is the whole identity here.
  • GET /public/marketPublic MarketTHE PLATFORM-WIDE MARKET (TJ3, the Emperor's demand engine): every published tenant's products, services and courses, browsable by anyone - Fitinty as the mall, not just the landlord. Every buyer any tenant attracts becomes a potential customer of every other. The same public-read boundary as every /site page, applied across orgs: only content of organizations with a LIVE theme appears (a storefr…
  • POST /public/marketplace/checkoutStart Marketplace CheckoutPUBLIC - guests buy too (LST5's ruling). The cart's state is read from Medusa, never trusted from the caller; the seller and the amount come from rows.
  • GET /public/marketplace/checkout/{checkout_id}Checkout StatusThe thanks page's reading: paid or not, order formed or forming - in words. The id is unguessable and the answer reveals what the buyer already knows.
  • POST /public/marketplace/checkout/{checkout_id}/place-orderRetry Place OrderThe retry door for a paid checkout whose order has not formed.
  • GET /public/marketplace/factsMarketplace Card FactsM-walk fix: the marketplace browse grid rendered cards with NO price and NO seller - a buyer deciding blind. This is the mall's own mirror read (the same rows /public/market serves), shaped as a map for the browse cards: who sells it, where its storefront page is, and whether a published full-story page exists.
  • GET /public/marketplace/promotionsPromoted RailTHE SPOTLIGHT RAIL for the browse screens: live promotions with their product facts, every one carrying promoted=true - the label IS the honesty.
  • POST /public/marketplace/stripe-webhookMarketplace Stripe Webhook
  • GET /public/media/access/{access_token}Check AccessWhat this key opens - for the public player to decide whether to show the unlocked state. An episode key names its episode; a membership key names its SHOW (it opens every premium episode there). Paid or not, in words; never the sales book.
  • POST /public/media/episodes/{episode_id}/buyBuy EpisodeA listener buys one premium episode. No account needed - a podcast listener rarely has one (the marketplace guest-checkout ruling, applied here). The email is where the unlock link goes conceptually; today the success redirect carries the token and the email is the purchase's honest record of WHO bought.
  • POST /public/media/episodes/{episode_id}/tipTip EpisodeA gratuity on any published episode - free or premium. The whole amount is the tenant's (fee 0, the SL3 ruling: nobody's thank-you carries a cut) and it never enters the sales ledger.
  • GET /public/media/license/{token}License OfferWhat the licensee reads before paying: the terms in the tenant's own words.
  • GET /public/media/license/{token}/downloadDownload Licensed FileThe delivery: the file moves only for CAPTURED money - the same wall the premium episode stands behind.
  • POST /public/media/license/{token}/payPay License
  • POST /public/media/shows/{show_id}/membershipJoin MembershipA listener becomes a monthly member of the show, on the show owner's own Stripe account (mode=subscription). The membership row is the STANDING record: paid = active, void = cancelled; money lands one row per invoice cycle.
  • GET /public/media/sponsor/{token}Sponsor OfferWhat the sponsor sees before paying: the facts in plain words, nothing else.
  • POST /public/media/sponsor/{token}/payPay Sponsorship
  • POST /public/platform/audienceJoin Platform AudienceThe join door on fitinty.com itself: the platform's own list, kept on the Fitinty organization with the SAME machinery tenants get - the Emperor markets Fitinty with the tools he sells. (The tenant join door requires a published storefront; the platform's own pages ARE its storefront, so this door checks only that the one org exists.)
  • GET /public/platform/pressPress KitThe public press page's data: the Emperor's words plus figures MEASURED at this moment from the platform's own tables - never a stored total, never a projection.
  • GET /public/platform/social-linksPlatform Social Links
  • GET /public/platform/testimonialsPublic Testimonials
  • GET /public/podcast-episodes/{episode_id}/audioPublic Audio
  • GET /public/podcast-shows/{show_id}/artworkPublic Artwork
  • GET /public/presentation-factory/shared/{token}Shared Page
  • POST /public/presentation-factory/shared/{token}/commentsShared Comment
  • GET /public/presentation-factory/shared/{token}/deckShared Asset
  • POST /public/presentation-factory/shared/{token}/reportShared Report
  • GET /public/presentation-followup/{token}Followup PagePUBLIC, no account: the people who were in the room read what they were promised. Only answered AND published items appear, each labelled if it has no source behind it.
  • POST /public/presentation-sessions/joinJoin
  • POST /public/presentation-sessions/{sid}/questionsAsk
  • POST /public/presentation-sessions/{sid}/questions/{qid}:upvoteUpvote
  • POST /public/presentation-sessions/{sid}/runs/{run_id}/answerAnswer Poll
  • GET /public/presentation-sessions/{sid}/stateAudience StatePolled every couple of seconds by every phone in the room, so it stays small: compare state_version and only re-render when it moved.
  • GET /public/pricing/plansPublic PlansThe price list, for anybody. Only `is_public` plans: `founding-all` is what every existing organization silently resolves to and is NOT for sale, so publishing it would advertise a free everything-tier.
  • GET /public/reports/source/{token}Public SourceR4 - the page the document server fetches to convert: a SHORT-LIVED signed token names ONE report; the token is minted only inside the export door, never shown to a person. Read on the system cursor because the document server is nobody.
  • GET /public/review-media/{review_id}Review Media
  • GET /public/reviews/course/{course_id}Course Reviews
  • GET /public/reviews/platformPlatform Reviews
  • GET /public/reviews/practice/{office_id}Practice Reviews
  • GET /public/reviews/service/{listing_id}Service Reviews
  • GET /public/services/offers/{token}Read Offer
  • POST /public/services/offers/{token}/acceptAccept OfferAccepting turns the offer into a booking INTENT and hands back the claim path - the existing claim flow signs the person in and places the booking with all its standing guards. Only a time the offer actually named is accepted.
  • GET /public/services/quotes/{token}Read Quote
  • GET /public/site/domain-allowedDomain AllowedCaddy on-demand-TLS ask endpoint. 200 = may request a certificate, 403 = must not. Caddy treats ANY 2xx as permission, so this must never return 200 on the error path - hence an explicit 403 rather than a body the caller has to interpret.
  • GET /public/site/resolve-domainResolve DomainCustom-domain routing: maps a Host header (port already stripped by the caller) to an org slug -- but ONLY for a domain whose ownership was proved (migration 261). Two independent gates, both required. The domain must be the LIVE theme version's developer.domainOverrides.publicDomain, so an unpublished change never reroutes traffic; and it must have a VERIFIED claim owned by that same organizatio…
  • GET /public/site/tls-askTls AskCaddy on-demand TLS 'ask' endpoint: Caddy calls this with ?domain=<host> before issuing a certificate for an unknown domain. THIS is the endpoint prod's Caddyfile actually points at (/public/site/tls-ask), so the ownership gate had to land here rather than only on the newer /public/site/domain-allowed - otherwise migration 261 would have shipped while the live certificate path kept using the weak…
  • GET /public/site/{slug}Get Public SiteBOTH HALVES, KEPT. The storefront reads the two doors above so each gets the caching it deserves; this one stays because it is a public contract older than the split and nothing is served by breaking a caller to make a point. It is assembled from the same one definition, so the three can never disagree.
  • POST /public/site/{slug}/audienceJoin AudienceThe join door. A rejoin after unsubscribing is a RE-CONSENT: the row updates, the unsubscribe clears, nothing doubles.
  • POST /public/site/{slug}/audience/leaveLeave AudienceThe same sentence whether or not the email was ever on the list - this door is not an oracle for probing who subscribes to what.
  • GET /public/site/{slug}/communityPublic CommunityThe open room: published announcements + clubs with real member counts. Anonymous read - the room's life IS the invitation.
  • GET /public/site/{slug}/community/clubs/{club_id}Public Club
  • GET /public/site/{slug}/contentGet Public Site ContentTHE HALF THAT MAY NEVER BE CACHED. Courses, services, events and products - everything a tenant can switch off, and the exact payload the sixty-second window used to hold on to.
  • GET /public/site/{slug}/courses/{course_id}Get Public Course
  • GET /public/site/{slug}/events/{event_id}Get Public Event
  • GET /public/site/{slug}/feed.xmlStore Product FeedThe Google Merchant Center product feed (RSS 2.0 + g: namespace). Only items Google would accept ride; the excluded count is stated in the feed itself so an empty-looking feed is never a mystery.
  • GET /public/site/{slug}/game-demosList Game Demos
  • GET /public/site/{slug}/game-demos/{world_id}Get Game Demo
  • POST /public/site/{slug}/intentsCreate Intent
  • GET /public/site/{slug}/jobsPublic JobsPublished openings on a LIVE storefront - the same gate as products: a shop that 404s cannot hire through its window.
  • POST /public/site/{slug}/jobs/{post_id}/applyPublic ApplyA stranger applies - no account (the guest-checkout ruling's spirit). The one demand is reachability: an application with no email AND no phone is a name we can never answer.
  • GET /public/site/{slug}/landing/{product_id}Public LandingThe public showcase: the PUBLISHED page plus the product's real buy facts. The CTA carries the same product path the catalog uses - one click to the buy flow.
  • GET /public/site/{slug}/logoGet Public Site Logo
  • GET /public/site/{slug}/partnersList Site PartnersThe partners this business chose to show on ITS OWN storefront. The other side's toggle governs the other side's page - each tenant designs only their own.
  • POST /public/site/{slug}/partners/{partnership_id}/visitRecord Partner VisitA visitor follows a partner card. The click lands in the append-only evidence ledger - the referral counts both owners read are counts of these rows - and the visitor gets the partner's public address back. No visitor identity is recorded: a click is a click.
  • GET /public/site/{slug}/pickup-locationsPickup LocationsST2: the checkout's pickup choices - the org's active physical places, on a LIVE storefront (the same gate as every public window).
  • GET /public/site/{slug}/podcastsPublic Shows
  • GET /public/site/{slug}/podcasts/{show_slug}/feed.xmlPublic Feed
  • GET /public/site/{slug}/postsPublic Posts
  • GET /public/site/{slug}/posts/{post_slug}Public Post
  • GET /public/site/{slug}/practicePublic Practice
  • POST /public/site/{slug}/practice/askPublic Practice AskThe question door: a stranger's ask becomes ONE office_lead (source 'question') through the moat's one writer, fenced to the practice the slug resolves to. Consent is recorded as the fact it is; without it the door refuses in words.
  • POST /public/site/{slug}/practice/bookPublic Practice BookA consultation booked through the door. Signed-in only: booking_insert demands the client's own id (no anonymous write ever creates an appointment - the intent-claim posture). The time must be one the door actually offers (409 otherwise), the row is written on the CLIENT's cursor, and its lead is minted once (source 'booking').
  • POST /public/site/{slug}/practice/joinPublic Practice JoinOF8 - the practice page's "keep me posted" door: not ready to ask or book, but wants to hear. The email joins the org's ONE audience list (G2's table, the games door's shape) with the consent words recorded and source 'practice' - the CHECK widened WITH this caller (526). Consent is a real yes or a 422; the closed loop then measures this door like any other.
  • GET /public/site/{slug}/practice/slotsPublic Practice Slots
  • GET /public/site/{slug}/productsList Public ProductsThe CATALOG the storefront never had (LST1) - browse, search, filter by category, paginate. Reads only the public-safe mirror; published rows of this org alone. The category list rides along so the browse bar renders from real data, never a taxonomy.
  • GET /public/site/{slug}/products/{product_id}Get Public ProductPublic product detail (STOREFRONT_GAPS: "Product detail pages / purchase links"). Reads ONLY the public-safe mirror (marketplace_public_product) - never Medusa or the credentials-bearing vendor row - and only ACTIVE rows of THIS org, behind the same live-theme gate as every other public page. Purchase itself stays where it already works: the signed-in Marketplace (test mode until Stripe live sign-…
  • GET /public/site/{slug}/products/{product_id}/also-boughtAlso BoughtG4 - the bought-together shelf: pairs counted from REAL order lines by the pairing agent, joined to the published mirror so a withdrawn product never rides along. The reading's age is stated - a stale shelf says so instead of pretending.
  • GET /public/site/{slug}/products/{product_id}/questionsList Product QuestionsPublic Q&A (441): questions and their answers publish together; a hidden question shows nothing. Unanswered shows AS unanswered - honest pressure.
  • POST /public/site/{slug}/products/{product_id}/questionsAsk Product QuestionAnyone may ask (a question is a sale mid-decision); rate-limited per IP; only the seller answers; the seller's hide takes a REASON - the Commons' hand, not an eraser.
  • GET /public/site/{slug}/products/{product_id}/reviewsList Product ReviewsPublic reviews for one product: the list, the average, the count - every one of them a verified purchase by construction (no other kind can exist in the table).
  • POST /public/site/{slug}/products/{product_id}/reviewsSubmit Product ReviewVERIFIED OR REFUSED (440, generalized by 442). The proof is the order id + the order's own email, checked against the store's real records; the email is checked and DISCARDED. One review per order per product. A seller cannot write their own reviews - there is no tenant door. The reviewer may attach THEMSELF saying it: photo, voice note or video (his ruling on recorded formats), size-capped, serve…
  • POST /public/site/{slug}/referralMint ReferralA customer's personal code - ONE per person per store; asking again returns the same code (the row is the truth, not the request).
  • GET /public/site/{slug}/referral-programPublic Program
  • GET /public/site/{slug}/referral/{code}Referral StatusThe referrer's window, keyed by the code itself - the secret only they hold. No email lookup exists on purpose (an email is guessable; a code is not).
  • GET /public/site/{slug}/services/{listing_id}Get Public Service
  • GET /public/site/{slug}/services/{listing_id}/imageGet Public Service ImageA service listing's image, behind the same gates as the listing itself: the site must be live and the listing active. Served from the API's own disk (no object storage exists).
  • GET /public/site/{slug}/services/{listing_id}/slotsPublic SlotsReal open slots for one listing on one day - windows sliced by the listing's own duration, minus pending/confirmed bookings. Past slots (for today) are excluded.
  • GET /public/site/{slug}/sitemap.xmlStore Sitemap XmlThe storefront's own sitemap - `sitemap_urls`, the one list of its pages. Live theme required - a site that 404s must not invite a crawler.
  • GET /public/site/{slug}/social-linksSite Social Links
  • POST /public/site/{slug}/subscribeSubscribe
  • GET /public/site/{slug}/themeGet Public Site ThemeTHE HALF THAT MAY BE CACHED (his ruling, 2026-09-22). A storefront was serving what its tenant had switched off for up to a minute, because theme AND content came back in ONE payload on one sixty-second cache entry - so the only ways to make the content fresh were to give up the theme's cache with it, or to split them. He ruled the split: the chrome is cached, the content is never cached, and a t…
  • POST /public/site/{slug}/unsubscribeUnsubscribeToken-matched unsubscribe via a SECURITY DEFINER helper (migration 243): an UPDATE's WHERE reads rows under SELECT policies, and subscriber rows deliberately have no public SELECT - emails are PII. The helper does the one narrow update without ever making the table publicly readable.
  • POST /public/site/{slug}/visitRecord VisitPrivacy-light page-view counter, called fire-and-forget by the web middleware. Stores nothing about the visitor -- one daily counter per (org, page, kind). The visitor's User-Agent arrives as X-Visitor-Agent (the middleware forwards the original request's header, since this call is server-to-server and its own UA would be Node's), is used ONLY to decide human vs bot, and is never written anywhere…
  • GET /public/sitemapPublic SitemapEverything a search engine may index: every org with a PUBLISHED theme, plus the ids of its public detail pages. Same public-read RLS boundary as the pages themselves -- nothing appears here that the storefront wouldn't serve.
  • GET /public/social-oauth/callbackOauth Callback
  • GET /public/song-factory/shared/{token}Shared Page
  • GET /public/song-factory/shared/{token}/audioShared Asset
  • POST /public/song-factory/shared/{token}/commentsShared Comment
  • POST /public/song-factory/shared/{token}/reportShared Report
  • GET /public/system/healthSystem Health
  • GET /public/talk/{token}Talk PagePUBLIC, no account: for the person who missed the room. Returns the pinned slides, the timeline the player needs, and the voice label - and counts the open, which is all a link can honestly know.
  • GET /public/talk/{token}/audioTalk Audio
  • GET /public/talk/{token}/captionsTalk Captions
  • GET /public/talk/{token}/transcriptTalk TranscriptReading instead of listening is a real accessibility need, not a lesser option.
  • GET /public/tax/copy/{token}Public Copy
  • GET /public/tax/identity/{token}Public Describe
  • POST /public/tax/identity/{token}Public Submit
  • POST /public/tenant-draftCreate Tenant DraftThe Convince moment: words in, a business standing at a preview URL out.
  • GET /public/tenant-draft/{token}Get Tenant Draft
  • PATCH /public/tenant-draft/{token}Change Draft PillarThe preview's override: the visitor corrects WHERE their business belongs before claiming it. The token is the authority (it is the same secret that claims), an already- claimed draft refuses, and the pillar must exist - free text would be a silent zero.
  • POST /public/tradingview/{token}Receive AlertReceive a TradingView alert and turn it into a trade intent. Returns 200 with an outcome for anything it understood well enough to record, including refusals - TradingView retries on non-2xx and a retried alert is indistinguishable from a replay, so refusing loudly with a 4xx would produce exactly the traffic this endpoint is built to reject. An unknown token is the one exception: it gets a flat …
  • GET /public/unsubscribe/{token}Unsubscribe Info
  • POST /public/unsubscribe/{token}Unsubscribe
  • POST /public/verifyVerify PastedRe-computes the canonical hash and HMAC over the pasted payload. A single changed character in the payload flips both answers to false.
  • GET /public/verify/file/{sha256}Verify By FileThe strongest check: hash the file you were given (the page does it in your browser - the file never leaves your machine) and ask whether this installation certified that exact file.
  • GET /public/verify/{cert_id}Verify By Id
  • GET /public/video-factory/shared/{token}Shared Page
  • GET /public/video-factory/shared/{token}/captionsShared Captions
  • POST /public/video-factory/shared/{token}/commentsShared Comment
  • POST /public/video-factory/shared/{token}/reportShared Report
  • GET /public/video-factory/shared/{token}/videoShared Asset
  • GET /public/workspace-documents/shared/{token}Shared Page
  • POST /public/workspace-documents/shared/{token}/commentsShared Comment
  • GET /public/workspace-documents/shared/{token}/fileShared Asset
  • POST /public/workspace-documents/shared/{token}/reportShared Report

the served openapi document - this reference is never written by hand

Pricing