{"components":{"responses":{"InvalidDatasetQuery":{"description":"A parameter is unknown, blank, repeated, malformed, or unsupported; a filter is not available on this dataset; or a data.{key} name contains unsupported characters.","content":{"application/json":{"example":{"data":null,"error":true,"message":"Unknown filter column \"provider\".","columns":["id","name","source","state","city"]}}}},"InvalidRowId":{"description":"The row id is not a valid School’s Out UUID, or the request includes a query parameter. Record lookup accepts path parameters only.","content":{"application/json":{"example":{"data":null,"error":true,"message":"Field \"id\" must be the row uuid."}}}},"NoQueryParameters":{"description":"The facets endpoint received a query parameter. Facets are returned for the complete tier-visible dataset window and cannot be narrowed.","content":{"application/json":{"example":{"data":null,"error":true,"message":"Unknown query parameter \"state\".","parameters":[]}}}},"InvalidSearchQuery":{"description":"q is missing, empty, too long, or provided more than five times; a parameter is unknown, blank, repeated, or malformed; or an exact filter is unavailable on the selected dataset.","content":{"application/json":{"example":{"data":null,"error":true,"message":"Query parameter \"limit\" must be between 1 and 200."}}}},"SearchTimeout":{"description":"Search exceeded its five-second database limit. Narrow the request with dataset or an exact filter before retrying.","content":{"application/json":{"example":{"data":null,"error":true,"message":"Search took too long. Narrow the query with a dataset or exact filter and try again."}}}},"SearchBusy":{"description":"Search capacity is busy. Wait one second before retrying.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","example":1}}},"content":{"application/json":{"example":{"data":null,"error":true,"message":"Search is busy. Try again in a moment.","retryAfterMs":1000}}}},"Unauthorized":{"description":"The x-api-key header is missing or the key is no longer valid.","content":{"application/json":{"example":{"authorized":false}}}},"PlanRequired":{"description":"The organization catalog requires Starter or above.","content":{"application/json":{"example":{"data":null,"error":true,"message":"The Data Explorer opens with Starter. Your Ask questions stay available on the Free plan.","upgradeRequired":true,"tier":"free","requiredTier":"paid","upgrade":"/api/billing/checkout"}}}},"Forbidden":{"description":"Your plan does not include the dataset, field, or result window.","content":{"application/json":{"example":{"data":null,"error":true,"message":"Free accounts receive 403: the Data Explorer opens with the Starter plan.","upgradeRequired":true,"tier":"free","resultLimit":1}}}},"NotFound":{"description":"The dataset or row does not exist.","content":{"application/json":{"example":{"data":null,"error":true,"message":"Unknown insights dataset."}}}},"ServerError":{"description":"The API failed while handling a valid request.","content":{"application/json":{"example":{"data":null,"error":true,"message":"Database request failed."}}}},"BookingsDisabled":{"description":"Bookings are off on this API (`BOOKINGS_ENABLED` isn’t `true`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"not found."}}}},"BookingNotFound":{"description":"No such booking for this parent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Booking not found.","data":null}}}},"FamilyRequestLimit":{"description":"The signed-in family account is making requests too quickly. Family sessions are not metered by the data-API read allotment; they share one abuse limit per account across all its sessions. Wait for Retry-After and try again.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","example":12}}},"content":{"application/json":{"schema":{"type":"object","required":["error","message","retryAfterMs","windowMs"],"properties":{"error":{"type":"boolean","const":true},"message":{"type":"string"},"data":{"type":"null"},"retryAfterMs":{"type":"integer","minimum":0,"description":"Milliseconds until another request is allowed."},"windowMs":{"type":"integer","minimum":1,"description":"Length of the limit's window in milliseconds."}}},"example":{"data":null,"error":true,"message":"This account is making requests too quickly. Wait for Retry-After and try again.","retryAfterMs":12000,"windowMs":60000}}}}},"schemas":{"AssistantEvidenceAssessment":{"additionalProperties":false,"description":"Assessment of the returned permission-filtered row. Coverage and aggregate organization metadata are not verification of free-text claims. Historical saved responses may predate this field.","properties":{"facts":{"additionalProperties":false,"properties":{"ages":{"additionalProperties":true,"properties":{"fields":{"items":{"type":"string"},"type":"array"},"state":{"enum":["present","partial","missing","unchecked"]},"unscopedFields":{"description":"Ambiguous bare shared columns in a joined row, excluded from this entity’s coverage.","items":{"type":"string"},"type":"array"}},"required":["state","fields"],"type":"object"},"availability":{"additionalProperties":true,"properties":{"currentPlacesConfirmed":{"const":false},"fields":{"items":{"type":"string"},"type":"array"},"state":{"enum":["present","partial","missing","unchecked"]},"unscopedFields":{"description":"Ambiguous bare shared columns in a joined row, excluded from this entity’s coverage.","items":{"type":"string"},"type":"array"}},"required":["state","fields"],"type":"object"},"hours":{"additionalProperties":true,"properties":{"fields":{"items":{"type":"string"},"type":"array"},"state":{"enum":["present","partial","missing","unchecked"]},"unscopedFields":{"description":"Ambiguous bare shared columns in a joined row, excluded from this entity’s coverage.","items":{"type":"string"},"type":"array"}},"required":["state","fields"],"type":"object"},"price":{"additionalProperties":true,"properties":{"budgetComparable":{"const":false},"currency":{"enum":["present","missing","unchecked"]},"fields":{"items":{"type":"string"},"type":"array"},"mandatoryFees":{"const":"unknown"},"period":{"const":"unknown"},"state":{"enum":["present","partial","missing","unchecked"]},"unscopedFields":{"description":"Ambiguous bare shared columns in a joined row, excluded from this entity’s coverage.","items":{"type":"string"},"type":"array"}},"required":["state","fields"],"type":"object"}},"required":["price","ages","hours","availability"],"type":"object"},"limitations":{"items":{"type":"string"},"type":"array"},"organization":{"additionalProperties":false,"properties":{"calculatedAt":{"description":"Score calculation time; never treated as source verification.","type":["string","null"]},"caps":{"items":{"type":"object"},"type":["array","null"]},"confidence":{"description":"Selected existing organization score; null when unavailable.","type":["number","null"]},"flags":{"items":{"type":"object"},"type":["array","null"]},"freshness":{"description":"Selected existing organization score; null when unavailable.","type":["number","null"]},"lastObservedAt":{"description":"Newest selected organization evidence date; not a fact-specific check.","type":["string","null"]},"missingFieldCount":{"type":["number","null"]},"scope":{"const":"organization"},"sourceCount":{"type":["number","null"]},"state":{"description":"Observed means an observation date was supplied, not that individual listing facts are fresh.","enum":["unknown","observed","stale"]}},"required":["scope","state","freshness","confidence","lastObservedAt","calculatedAt","caps","flags","sourceCount","missingFieldCount"],"type":"object"},"scope":{"description":"Entity whose selected facts are assessed. Parent and child fields do not fill each other’s gaps.","enum":["organization","activity","session","listing"]}},"required":["scope","organization","facts","limitations"],"type":"object"},"CanonicalDetailRow":{"additionalProperties":true,"properties":{"after_care_pricing":{"$ref":"#/components/schemas/CarePricing","readOnly":true,"description":"Read-only compatibility quote when every prices option records the same after-care quote. Unknown when any option has unknown or different pricing. Edit the quote in prices."},"before_care_pricing":{"$ref":"#/components/schemas/CarePricing","readOnly":true,"description":"Read-only compatibility quote when every prices option records the same before-care quote. Unknown when any option has unknown or different pricing. Edit the quote in prices."},"registration_close_date":{"description":"Session-owned last date this session accepts registrations; it has no activity or organization fallback.","format":"date","type":"string"},"registration_open_date":{"description":"Session-owned first date this session accepts registrations; it has no activity or organization fallback.","format":"date","type":"string"},"schedule_configurations":{"description":"Authoritative attendance configurations using explicit dates or bounded weekly date_rule values. Omission means no structured schedule is available; empty arrays are invalid. The whole collection is one reversible contribution. Rules remain rules in storage and responses; derived bounds do not imply attendance on intervening dates. Pricing inside configurations follows the same access policy as other prices.","items":{"$ref":"#/components/schemas/SessionScheduleConfiguration"},"minItems":1,"type":"array"},"prices":{"description":"Ordered session enrollment options with optional source-grounded descriptions and before- and after-care quotes. A care-only option leaves tuition unknown. An empty array means no recorded price; the complete collection, including descriptions and care quotes, is one canonical field.","items":{"$ref":"#/components/schemas/SessionPrice"},"type":"array"},"resolved_cover":{"anyOf":[{"$ref":"#/components/schemas/CanonicalResolvedCover"},{"type":"null"}],"description":"Present on permitted ordinary JSON catalog reads, including includeMedia=false; omitted from previews and named aggregate reads."},"removed":{"type":"boolean","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_survivor_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"Present only with authorized showRemoved=true. Optional active same-kind survivor linked from the removed root; no data, descendants, or history move to it."},"removal_batch_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_review_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_review_version":{"type":["integer","null"],"readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_reason":{"type":["string","null"],"readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"field_resolution":{"$ref":"#/components/schemas/CanonicalFieldResolution","description":"Present when includeResolution=true; current effective state for the row fields allowed by the caller projection."},"field_sources":{"$ref":"#/components/schemas/CanonicalFieldSources","description":"Present when includeSources=true; current effective source identity for the row fields allowed by the caller projection."},"own_values":{"$ref":"#/components/schemas/CanonicalOwnValues","description":"Present when authorized includeOwnValues=true; entity-owned values, including nulls."},"booking_url":{"type":"string","nullable":true,"format":"uri","description":"Session-only enrollment entrypoint with sourced evidence. No parent fallback. Enterprise visibility."},"booking_fields":{"allOf":[{"$ref":"#/components/schemas/SessionBookingFields"}],"nullable":true,"description":"Session-only sourced requirements. Null means not recorded. Enterprise visibility."}},"type":"object","description":"Sessions own registration_open_date, registration_close_date, an ordered prices collection, and nullable freeform `discounts` text; attendance dates and clocks belong to schedule_configurations. Activities have no enrollment price, currency, discount, or care-pricing fields. Activity image_url is a compatibility read alias for the selected active cover only. Each session price option may include a source-grounded description and before_care_pricing and after_care_pricing quotes; blank descriptions are omitted and no default label is inferred. Session-level care fields are read-only compatibility summaries when all options share a quote. Schedule configurations retain their own independent care quotes. Normal JSON records omit bookkeeping and ingestion history, null and blank record fields, and unknown availability, registration-status, and coordinate-precision values. Zero, false, empty arrays, masked values, positional range bounds, and structured schedule states retain their meanings. Related entity summaries follow the same projection; CSV retains its existing contract. Nonblank configuration_id is retained as record identity metadata for feedback targeting and omitted from display column metadata. Moved fields are external_ids, created_at, updated_at, source_created_at, field_provenance, source_records, merge_jobs, and relationship_jobs. Age ranges use age_range_years; normal JSON records and column metadata omit the redundant age_range_months alias. CSV and the legacy ageMonths filter retain month compatibility. Records omit score; fetch the separate score endpoints when needed."},"CanonicalFieldProvenance":{"additionalProperties":{"additionalProperties":false,"properties":{"history":{"description":"Newest decisions first; the first entry is the latest decision. Current owners and the latest observation may predate this bounded history.","items":{"$ref":"#/components/schemas/FieldProvenanceEntry"},"type":"array"},"latest_observation":{"$ref":"#/components/schemas/FieldProvenanceEntry"},"sources":{"items":{"$ref":"#/components/schemas/FieldProvenanceEntry"},"type":"array"},"state":{"description":"tracked means complete current-value coverage; partial means some range endpoints or supporting links have coverage; unknown has no current source owner.","enum":["tracked","partial","unknown"],"type":"string"},"value_state":{"description":"Current accepted field action, independent of whether a source check ran.","enum":["set","cleared","unknown"],"type":"string"}},"required":["state","sources","value_state"],"type":"object"},"description":"Present only for ADMIN user-backed reads of the dedicated provenance endpoint. Keys are visible display columns before sparse projection, including unset fields. state and sources describe current accepted-value ownership; value_state records whether the accepted value is set, cleared, or unknown. latest_observation and decision history are separate: checked_not_found, not_applicable, failed, and incomplete do not clear a value. No observation means no check was recorded. Older contributions are read without inferred checks. Field citations are distinct from a website scrape root, whose scope is site.","type":"object"},"ProvenanceTimelineOrganizationScore":{"description":"On-demand parent organization score with the currently deployed rubric. This is reconstructed data valuation, not an immutable originally served score. Missing historical inputs use rubric defaults and are disclosed; no current score is substituted.","oneOf":[{"type":"object","required":["state"],"properties":{"state":{"const":"restricted"}},"additionalProperties":false},{"type":"object","required":["state","evaluatedAt","scoringVersion","reason"],"properties":{"state":{"const":"unavailable"},"evaluatedAt":{"type":"string","format":"date-time","description":"Inclusive reconstruction cutoff, identical to entry recorded_at; rubric evaluation uses this time."},"scoringVersion":{"type":"string"},"reason":{"type":"string"}}},{"type":"object","required":["state","evaluatedAt","scoringVersion","overall","band","confidence","interval","caps","sourceCount","coverage"],"properties":{"state":{"const":"reconstructed"},"evaluatedAt":{"type":"string","format":"date-time","description":"Inclusive reconstruction cutoff, identical to entry recorded_at; rubric evaluation uses this time."},"scoringVersion":{"type":"string"},"overall":{"type":"number","minimum":0,"maximum":100},"band":{"enum":["weak","fair","good","strong"]},"confidence":{"type":"number","minimum":0,"maximum":100},"interval":{"type":"object","required":["low","high"],"properties":{"low":{"type":"number","minimum":0,"maximum":100},"high":{"type":"number","minimum":0,"maximum":100}}},"caps":{"type":"array","items":{"type":"object","required":["criterion","ceiling","reason"],"properties":{"criterion":{"type":"string"},"ceiling":{"type":"number","minimum":0,"maximum":100},"reason":{"type":"string"}}}},"sourceCount":{"type":"integer","minimum":0},"coverage":{"type":"object","required":["history","children","missingInputs"],"properties":{"history":{"const":"retained_canonical_contributions"},"children":{"const":"current_associations"},"missingInputs":{"type":"array","items":{"type":"string"},"description":"Machine-readable reconstruction limitations; clients must tolerate new codes."},"fallbackFields":{"type":"array","items":{"type":"string"},"description":"Qualified fields using current canonical values as an approximate baseline because no accepted history is retained for that field at any time."}}}}}]},"ProvenanceTimelineValue":{"description":"One before or after side. Only the available state carries a value; value accepts any JSON value, including explicit null. For media fields, an available value is an array of ProvenanceTimelineMediaItem, with no source attribution; an empty array means known empty.","oneOf":[{"additionalProperties":false,"properties":{"state":{"const":"available"},"value":{}},"required":["state","value"],"type":"object"},{"additionalProperties":false,"properties":{"state":{"const":"unavailable"}},"required":["state"],"type":"object"},{"additionalProperties":false,"properties":{"state":{"const":"restricted"}},"required":["state"],"type":"object"}]},"ProvenanceTimelineMediaItem":{"additionalProperties":false,"description":"Sanitized media page item in reconstructed timeline values. It contains no source or evidence provenance. Remote displayUrl values preserve signed query strings and fragments; same-entity uploads use the protected organization asset URL. Public stock credit is retained independently of private source evidence.","properties":{"displayUrl":{"format":"uri-reference","type":"string"},"height":{"type":["integer","null"]},"id":{"format":"uuid","type":"string"},"kind":{"type":"string"},"label":{"type":"string"},"width":{"type":["integer","null"]},"isStock":{"type":"boolean","description":"Derived from retained stock metadata."},"stock":{"anyOf":[{"$ref":"#/components/schemas/CanonicalStockImage"},{"type":"null"}]}},"required":["id","displayUrl","label","kind","width","height","isStock","stock"],"type":"object"},"CanonicalMediaItem":{"properties":{"displayUrl":{"description":"Original remote URL or a protected relative organization brand-asset URL.","format":"uri-reference","type":"string"},"height":{"minimum":1,"type":["integer","null"]},"id":{"format":"uuid","type":"string"},"kind":{"type":"string"},"label":{"type":"string"},"source":{"description":"Present only for a non-preview ADMIN account viewer.","items":{"$ref":"#/components/schemas/CanonicalMediaSource"},"type":"array"},"width":{"minimum":1,"type":["integer","null"]},"isStock":{"type":"boolean","description":"Derived from retained stock metadata."},"stock":{"anyOf":[{"$ref":"#/components/schemas/CanonicalStockImage"},{"type":"null"}]}},"required":["id","displayUrl","label","kind","isStock","stock"],"type":"object"},"CanonicalMediaPage":{"type":"object","description":"Exact-owner gallery page with independent resolvedCover for display. Borrowed covers retain sourceEntity and do not alter items, selected IDs or total. Stock credits are visible with the image; source evidence remains ADMIN-only.","properties":{"entity":{"type":"object","properties":{"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"}}},"items":{"type":"array","items":{"$ref":"#/components/schemas/CanonicalMediaItem"}},"coverId":{"type":["string","null"],"format":"uuid"},"logoId":{"type":["string","null"],"format":"uuid"},"total":{"type":"integer","minimum":0,"description":"Total number of visible distinct images."},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of images in this page."},"offset":{"type":"integer","minimum":0,"description":"Zero-based offset of this page; bundled detail starts at zero."},"resolvedCover":{"anyOf":[{"$ref":"#/components/schemas/CanonicalResolvedCover"},{"type":"null"}]}},"required":["entity","items","coverId","logoId","resolvedCover","total","limit","offset"]},"CanonicalMediaSource":{"properties":{"baseline":{"type":"boolean"},"observedAt":{"format":"date-time","type":"string"},"source":{"properties":{"kind":{"enum":["trident","manual","legacy","upload"],"type":"string"},"recordId":{"type":"string"},"website":{"format":"uri","type":"string"}},"required":["kind"],"type":"object"},"sourcePageUrl":{"format":"uri","type":"string"}},"type":"object"},"CanonicalReference":{"description":"Staff metadata only: live PostgreSQL storage structure, application field definitions, and editing and interpretation rules. No entity record values. Coverage issues identify drift or missing explanations; a successful response with issues is not a fully described model.","properties":{"applicationFingerprint":{"type":"string"},"coverageIssues":{"items":{"$ref":"#/components/schemas/CanonicalReferenceCoverageIssue"},"type":"array"},"entities":{"items":{"$ref":"#/components/schemas/CanonicalReferenceEntity"},"type":"array"},"generatedAt":{"format":"date-time","type":"string"},"relationships":{"items":{"$ref":"#/components/schemas/CanonicalReferenceRelationship"},"type":"array"},"rules":{"items":{"$ref":"#/components/schemas/CanonicalReferenceRule"},"type":"array"},"schemaFingerprint":{"type":"string"},"version":{"const":1,"type":"integer"}},"required":["version","generatedAt","schemaFingerprint","applicationFingerprint","entities","relationships","rules","coverageIssues"],"type":"object"},"CanonicalReferenceConstraint":{"properties":{"columns":{"items":{"type":"string"},"type":"array"},"definition":{"type":"string"},"name":{"type":"string"},"referencedColumns":{"items":{"type":"string"},"type":"array"},"referencedSchema":{"type":"string"},"referencedTable":{"type":"string"},"type":{"type":"string"}},"required":["name","type","definition","columns"],"type":"object"},"CanonicalReferenceCoverageIssue":{"properties":{"code":{"type":"string"},"entity":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}},"required":["entity","field","code","message"],"type":"object"},"CanonicalReferenceEditor":{"properties":{"inputType":{"type":"string"},"key":{"type":"string"},"max":{"type":"number"},"maxItems":{"type":"integer"},"maxLength":{"type":"integer"},"min":{"type":"number"},"options":{"items":{"$ref":"#/components/schemas/CanonicalReferenceOption"},"type":"array"},"required":{"type":"boolean"},"staffOnly":{"type":"boolean"},"step":{"type":"number"},"supportedBits":{"items":{"$ref":"#/components/schemas/CanonicalReferenceOption"},"type":"array"}},"required":["key","required","inputType","options","staffOnly"],"type":"object"},"CanonicalReferenceEntity":{"properties":{"description":{"type":"string"},"fields":{"items":{"$ref":"#/components/schemas/CanonicalReferenceField"},"type":"array"},"kind":{"type":"string"},"label":{"type":"string"},"table":{"type":"string"}},"required":["kind","label","description","table","fields"],"type":"object"},"CanonicalReferenceField":{"properties":{"aliasOf":{"type":"string"},"dataType":{"description":"Type of the API or editing value; storage-only entries use the database type. A JSON read representation does not imply a physical JSON column. Sources and storage describe the underlying tables and columns.","type":"string"},"description":{"type":"string"},"editor":{"$ref":"#/components/schemas/CanonicalReferenceEditor"},"group":{"type":"string"},"id":{"type":"string"},"inheritance":{"$ref":"#/components/schemas/CanonicalReferenceInheritance"},"kind":{"enum":["stored","derived","editing","compatibility","storage"],"type":"string"},"label":{"type":"string"},"meaningSource":{"type":"string"},"name":{"type":"string"},"nullable":{"type":"boolean"},"sources":{"items":{"$ref":"#/components/schemas/CanonicalReferenceSource"},"type":"array"},"storage":{"items":{"$ref":"#/components/schemas/CanonicalReferenceStorage"},"type":"array"}},"required":["id","name","label","description","group","dataType","kind","nullable","sources","storage","meaningSource"],"type":"object"},"CanonicalReferenceInheritance":{"description":"Effective configuration inheritance only. Independent fields and physical storage entries omit this metadata. Display-cover fallback is described by its separate interpretation rule.","properties":{"description":{"description":"Resolution, eligibility, explicit-clear, atomic-unit, and per-key merge rules for this field.","type":"string"},"local":{"description":"Whether canonical writers accept this record's own value for the configuration unit. Sessions cannot state registration_mode, offers, or staff_ratio.","type":"boolean"},"sources":{"description":"Eligible parent entity kinds in resolution order, excluding this entity kind. Empty for organizations; organization for activities; activity then organization for sessions. A session's activity must belong to the same organization, and its direct organization takes precedence.","items":{"enum":["organization","activity","session"],"type":"string"},"type":"array"},"unit":{"description":"Canonical configuration resolution unit, shared by all atomic components and compatibility projections. Offers resolve per key within their unit.","type":"string"}},"required":["unit","sources","local","description"],"type":"object"},"CanonicalReferenceOption":{"properties":{"label":{"type":"string"},"value":{}},"required":["value","label"],"type":"object"},"CanonicalReferenceRelationship":{"properties":{"field":{"type":"string"},"from":{"type":"string"},"label":{"type":"string"},"nullable":{"type":"boolean"},"to":{"type":"string"}},"required":["from","to","field","label","nullable"],"type":"object"},"CanonicalReferenceRule":{"properties":{"category":{"enum":["editing","interpretation"],"type":"string"},"description":{"type":"string"},"details":{"additionalProperties":true,"type":"object"},"entities":{"items":{"type":"string"},"type":"array"},"fields":{"items":{"type":"string"},"type":"array"},"id":{"type":"string"},"label":{"type":"string"},"source":{"type":"string"}},"required":["id","category","label","description","entities","fields","source"],"type":"object"},"CanonicalReferenceSource":{"properties":{"column":{"type":"string"},"table":{"type":"string"}},"required":["table","column"],"type":"object"},"CanonicalReferenceStorage":{"properties":{"column":{"type":"string"},"comment":{"type":"string"},"constraints":{"items":{"$ref":"#/components/schemas/CanonicalReferenceConstraint"},"type":"array"},"dataType":{"type":"string"},"default":{"type":["string","null"]},"enumValues":{"items":{"type":"string"},"type":"array"},"generated":{"type":"string"},"identity":{"type":"string"},"nullable":{"type":"boolean"},"primaryKey":{"type":"boolean"},"table":{"type":"string"}},"required":["table","column","dataType","nullable","default","comment","primaryKey","constraints","enumValues"],"type":"object"},"CarePricing":{"additionalProperties":false,"description":"One coherent before- or after-care quote; null means price unknown. Amount zero means explicitly free care. The quote is selected and reversed with its owning prices or schedule_configurations collection.","properties":{"amount":{"description":"Amount in major currency units, such as dollars rather than cents.","minimum":0,"type":"number"},"basis":{"description":"Stated price rate. session covers the entire enrollment; null means the rate is unknown. Never inferred from a price.","enum":["hour","day","week","month","session",null],"type":["string","null"]},"currency":{"description":"Three-letter currency code for this care quote, independent of tuition currency.","pattern":"^[A-Za-z]{3}$","type":"string"}},"required":["amount","currency"],"type":["object","null"]},"ChildGuardian":{"properties":{"email":{"type":"string"},"joinedAt":{"format":"date-time","type":"string"},"userId":{"format":"uuid","type":"string"}},"required":["userId","email","joinedAt"],"type":"object"},"ChildInvitation":{"properties":{"createdAt":{"format":"date-time","type":"string"},"email":{"format":"email","type":"string"},"expiresAt":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"}},"required":["id","email","createdAt","expiresAt"],"type":"object"},"ChildInvitationPreview":{"allOf":[{"$ref":"#/components/schemas/ChildInvitation"},{"properties":{"childId":{"format":"uuid","type":"string"},"childName":{"type":"string"},"invitedByEmail":{"type":"string"}},"required":["childId","childName","invitedByEmail"],"type":"object"}]},"Collection":{"additionalProperties":false,"properties":{"archivedAt":{"format":"date-time","type":["string","null"]},"availableMemberCount":{"description":"Nondeleted and nonblacklisted member camps currently available in the catalog.","minimum":0,"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"createdBy":{"description":"Null when the account that created the shared collection has been deleted; the collection and memberships remain.","format":"uuid","type":["string","null"]},"id":{"format":"uuid","type":"string"},"memberCount":{"description":"All retained camp memberships, including currently unavailable camps.","minimum":0,"type":"integer"},"name":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"visibility":{"enum":["private","public"],"description":"public collections can be opened by link, followed and featured. New staff collections are private."},"placement":{"type":"array","items":{"enum":["explore","businesses"]},"description":"Explore shelves a public collection appears on."},"position":{"type":["integer","null"],"minimum":0,"description":"Order on its shelves, lowest first."},"cover":{"oneOf":[{"$ref":"#/components/schemas/FamilySocialCollectionCover"},{"type":"null"}]}},"required":["id","name","createdBy","createdAt","updatedAt","archivedAt","memberCount","availableMemberCount","visibility","placement","position","cover"],"type":"object"},"CollectionInput":{"additionalProperties":false,"properties":{"name":{"description":"Single-line collection name. Active names are unique without regard to case.","maxLength":120,"minLength":1,"type":"string"}},"required":["name"],"type":"object"},"CollectionUpdateInput":{"additionalProperties":false,"minProperties":1,"properties":{"archived":{"const":true,"description":"Archive the collection; archive is permanent."},"name":{"maxLength":120,"minLength":1,"type":"string"},"visibility":{"enum":["private","public"]},"placement":{"type":"array","items":{"enum":["explore","businesses"]}},"position":{"type":["integer","null"],"minimum":0,"maximum":1000000,"description":"null clears it."},"cover":{"oneOf":[{"$ref":"#/components/schemas/FamilySocialCollectionCover"},{"type":"null"}],"description":"null clears it."}},"type":"object"},"FamilySocialItemSummary":{"type":"object","required":["key","kind","id","name","organizationId","organizationName","imageUrl","logoUrl","available","savedAt"],"properties":{"key":{"type":"string","description":"activity:<uuid> or organization:<uuid>.","pattern":"^(activity|organization):[0-9a-f-]{36}$"},"kind":{"enum":["activity","organization"]},"id":{"type":"string","format":"uuid"},"name":{"type":"string","description":"Empty only when the catalog record no longer exists."},"organizationId":{"type":"string","description":"The organization, or the record itself for an organization; empty when unknown."},"organizationName":{"type":"string"},"imageUrl":{"type":["string","null"],"description":"The record's first photo: its selected cover, else its first gallery photo. A logo is never a photo, so this is null when the record has no cover or gallery photo, or is unavailable."},"logoUrl":{"type":["string","null"],"description":"The selected logo of the item's organization (organizationId): an organization's own, an activity's organization's. Never a photo; null when it has none or the item is unavailable. Once rendered it is the logo's public, cross-origin rendition (GET /api/media/logos/{file}), else its source URL."},"available":{"type":"boolean","description":"False once the record is archived, removed or hidden from the catalog. Only the owner of a list sees unavailable items."},"savedAt":{"type":["string","null"],"format":"date-time","description":"When the viewer saved it; null when not in Saved or signed out."}},"additionalProperties":true},"FamilySocialCollectionOwner":{"oneOf":[{"type":"object","required":["kind"],"properties":{"kind":{"const":"you"}},"additionalProperties":false},{"type":"object","required":["kind","userId","publicName","publicNameKnown","photoUrl"],"properties":{"kind":{"const":"person"},"userId":{"type":"string","format":"uuid"},"publicName":{"type":["string","null"]},"publicNameKnown":{"type":"boolean","description":"False when the parent has no name to show; publicName is then null and the client writes its own wording."},"photoUrl":{"type":["string","null"],"description":"The photo the parent uploaded (an API path carrying its hash), else their Google picture (an absolute URL); null when there is neither."}},"additionalProperties":false},{"type":"object","required":["kind"],"properties":{"kind":{"const":"schoolsOut"}},"additionalProperties":false},{"type":"object","required":["kind","organizationId","name","logoUrl"],"properties":{"kind":{"const":"organization"},"organizationId":{"type":"string","format":"uuid"},"name":{"type":"string"},"logoUrl":{"type":["string","null"],"description":"The organization's selected logo; null when it has none. Once rendered it is the logo's public, cross-origin rendition (GET /api/media/logos/{file}), else its source URL."}},"additionalProperties":false}]},"FamilySocialCollectionCover":{"type":"object","required":["style","imageUrl","tone"],"properties":{"style":{"enum":["still-life","gradient","taped-print"]},"imageUrl":{"type":"string","description":"An https URL or a path on this API."},"tone":{"type":["string","null"],"enum":["purple","turquoise","orange","blue",null]}},"additionalProperties":false},"FamilySocialCollectionSummary":{"type":"object","required":["id","name","owner","visibility","verified","cover","counts","previewImages","following","hidden","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"owner":{"$ref":"#/components/schemas/FamilySocialCollectionOwner"},"visibility":{"enum":["private","public"]},"verified":{"type":"boolean","description":"Curated by School's Out or an organization."},"cover":{"oneOf":[{"$ref":"#/components/schemas/FamilySocialCollectionCover"},{"type":"null"}]},"counts":{"type":"object","required":["activities","organizations"],"properties":{"activities":{"type":"integer","minimum":0},"organizations":{"type":"integer","minimum":0}},"description":"Available items only.","additionalProperties":false},"previewImages":{"description":"The first photos of the newest available items that have one. A logo is never a preview.","type":"array","maxItems":4,"items":{"type":"string"}},"following":{"type":"boolean","description":"The viewer follows it; always false for the viewer's own."},"hidden":{"type":"boolean","description":"A collection you follow that you can no longer open, because its owner made it private or it was archived. It keeps its id, name and owner so you can see it and unfollow; it reads as private with no cover, zero counts, no preview images and no items, and its updatedAt is when you followed it."},"updatedAt":{"type":"string","format":"date-time"}},"additionalProperties":true},"FamilySocialCollectionDetail":{"allOf":[{"$ref":"#/components/schemas/FamilySocialCollectionSummary"},{"type":"object","required":["items","nextCursor"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/FamilySocialItemSummary"},"description":"Newest first."},"nextCursor":{"type":["string","null"],"description":"Opaque; pass as cursor for the next page. Null on the last page."}}}]},"FamilySocialFeaturedCollection":{"allOf":[{"$ref":"#/components/schemas/FamilySocialCollectionSummary"},{"type":"object","required":["items"],"properties":{"items":{"type":"array","maxItems":12,"items":{"$ref":"#/components/schemas/FamilySocialItemSummary"},"description":"The first available items, newest first."}}}]},"FamilySocialRestore":{"type":"object","required":["savedAt","collectionIds"],"properties":{"savedAt":{"type":"string","format":"date-time"},"collectionIds":{"type":"array","items":{"type":"string","format":"uuid"}}},"description":"What Undo sends back: savedAt to PUT saved/{itemKey}, collectionIds to PUT saved/{itemKey}/collections.","additionalProperties":false},"FamilySocialSavedItem":{"allOf":[{"$ref":"#/components/schemas/FamilySocialItemSummary"},{"type":"object","required":["collectionIds"],"properties":{"collectionIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"The viewer's own collections that hold the item, by ID; empty when none does."}}}]},"FamilySocialTrackedRecord":{"type":"object","required":["id","targetKind","targetId","created_at","available","alerts","name","city","region","organizationId","organizationName","activityId","activityName","startDate","endDate","startTime","endTime","scheduleConfigurations"],"properties":{"id":{"type":"string","format":"uuid"},"targetKind":{"enum":["organization","activity","session"]},"targetId":{"type":"string","format":"uuid"},"created_at":{"type":"string","description":"When the account started following."},"available":{"type":"boolean"},"alerts":{"type":"array","items":{"enum":["registration_opens","spot_opens","dates_posted","updates"]}},"name":{"type":["string","null"]},"city":{"type":["string","null"]},"region":{"type":["string","null"]},"organizationId":{"type":["string","null"]},"organizationName":{"type":["string","null"]},"activityId":{"type":["string","null"]},"activityName":{"type":["string","null"]},"startDate":{"type":["string","null"]},"endDate":{"type":["string","null"]},"startTime":{"type":["string","null"]},"endTime":{"type":["string","null"]},"scheduleConfigurations":{"type":["array","null"],"items":{}}},"additionalProperties":false},"FamilySocialPrivacy":{"type":"object","required":["sharePlans","childProfilesVisible","dataSharing"],"properties":{"sharePlans":{"type":"boolean","description":"Friends see what the family is going to."},"childProfilesVisible":{"type":"boolean","description":"Children's first names show on shared plans."},"dataSharing":{"type":"boolean","description":"Aggregate demand signals may use the family's activity."}},"additionalProperties":false},"FamilySocialProfile":{"type":"object","required":["publicName","publicNameKnown","photoUrl","privacy","version"],"properties":{"publicName":{"type":["string","null"],"description":"First name and last initial of the account's name (its legacy profile first and last name), else the preferred name, else the username; null when the account has none."},"publicNameKnown":{"type":"boolean","description":"False when the parent has no name to show; publicName is then null and the client writes its own wording."},"photoUrl":{"type":["string","null"],"description":"The photo the parent uploaded (an API path carrying its hash), else their Google picture (an absolute URL); null when there is neither."},"privacy":{"$ref":"#/components/schemas/FamilySocialPrivacy"},"version":{"type":"integer","minimum":0,"description":"0 until the first write."}},"additionalProperties":false},"FamilySocialRelation":{"enum":["none","requested","incoming","friends","blocked"]},"FamilySocialFriend":{"type":"object","required":["userId","publicName","publicNameKnown","photoUrl","relation","since"],"properties":{"userId":{"type":"string","format":"uuid"},"publicName":{"type":["string","null"]},"publicNameKnown":{"type":"boolean","description":"False when the parent has no name to show; publicName is then null and the client writes its own wording."},"photoUrl":{"type":["string","null"],"description":"The photo the parent uploaded (an API path carrying its hash), else their Google picture (an absolute URL); null when there is neither."},"relation":{"$ref":"#/components/schemas/FamilySocialRelation"},"since":{"type":"string","format":"date-time"}},"additionalProperties":false},"FamilySocialGoing":{"type":"object","required":["childFirstName","item","sessionId","startsOn"],"properties":{"childFirstName":{"type":["string","null"],"description":"Null unless the friend shows children's names."},"item":{"$ref":"#/components/schemas/FamilySocialItemSummary"},"sessionId":{"type":["string","null"],"format":"uuid"},"startsOn":{"type":["string","null"],"format":"date"}},"additionalProperties":false},"FamilySocialPerson":{"type":"object","required":["userId","publicName","publicNameKnown","photoUrl","relation","reported","collections","going"],"properties":{"userId":{"type":"string","format":"uuid"},"publicName":{"type":["string","null"]},"publicNameKnown":{"type":"boolean","description":"False when the parent has no name to show; publicName is then null and the client writes its own wording."},"photoUrl":{"type":["string","null"],"description":"The photo the parent uploaded (an API path carrying its hash), else their Google picture (an absolute URL); null when there is neither."},"relation":{"$ref":"#/components/schemas/FamilySocialRelation"},"reported":{"type":"boolean","description":"You have reported this parent."},"collections":{"type":"array","items":{"$ref":"#/components/schemas/FamilySocialCollectionSummary"},"description":"Their public collections."},"going":{"type":["array","null"],"items":{"$ref":"#/components/schemas/FamilySocialGoing"},"description":"Null unless you are friends and they share plans."}},"additionalProperties":false},"FamilySocialFriendsGoing":{"type":"object","required":["organizationId","activityId","sessionId","friends"],"properties":{"organizationId":{"type":"string","format":"uuid","description":"The business of the sign-ups; the asked ID when asked by organizationIds."},"activityId":{"type":["string","null"],"format":"uuid","description":"Set when asked by activityIds, and by sessionIds for a session that belongs to an activity; null when asked by organizationIds."},"sessionId":{"type":["string","null"],"format":"uuid","description":"Set when asked by sessionIds."},"friends":{"type":"array","items":{"type":"object","required":["userId","publicName","publicNameKnown","photoUrl","childFirstName"],"properties":{"userId":{"type":"string","format":"uuid"},"publicName":{"type":["string","null"]},"publicNameKnown":{"type":"boolean","description":"False when the parent has no name to show; publicName is then null and the client writes its own wording."},"photoUrl":{"type":["string","null"],"description":"The photo the parent uploaded (an API path carrying its hash), else their Google picture (an absolute URL); null when there is neither."},"childFirstName":{"type":["string","null"]}},"additionalProperties":false}}},"additionalProperties":false,"description":"The sharing friends who booked one asked record (a booked signal: friends who booked here, not the feed's still-running friend stories). A sign-up counts while its session is upcoming or ongoing, and for 365 days after its last day (the viewer's household dates), so a business's who-booked line covers past and upcoming bookings."},"FamilyModerationReport":{"type":"object","required":["id","reporterUserId","targetKind","targetId","reason","note","status","staffNote","reviewedBy","resolvedAt","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"reporterUserId":{"type":"string","format":"uuid"},"targetKind":{"enum":["person","activity","organization","collection"]},"targetId":{"type":"string","format":"uuid"},"reason":{"enum":["fake","inappropriate","spam","other"]},"note":{"type":["string","null"]},"status":{"enum":["open","reviewing","actioned","dismissed"]},"staffNote":{"type":["string","null"],"description":"Staff's note; never shown to the reporter."},"reviewedBy":{"type":["string","null"],"format":"uuid","description":"The staff account that last moved the report."},"resolvedAt":{"type":["string","null"],"format":"date-time","description":"Set exactly when actioned or dismissed."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"additionalProperties":false},"CoordinatePrecision":{"description":"Source quality, independent of numeric precision. Accessible coordinates retain full stored precision on every plan, without a guarantee of geographic correctness. exact covers complete existing/source pairs under the scraper-data assumption. null means no complete pair. ZIP representative points may lack a boundary.","enum":["exact","building","parcel","street","zip","state","unknown",null],"type":["string","null"]},"CrawlerEvaluation":{"additionalProperties":false,"properties":{"attempt_id":{"type":["string","null"],"description":"Execution attempt for this evaluation; distinct from source_attempt_id."},"baseline_evaluation_id":{"type":["string","null"]},"completion_reason":{"type":["string","null"]},"config":{"$ref":"#/components/schemas/CrawlerConfig"},"counts":{"$ref":"#/components/schemas/CrawlerEvaluationCounts"},"created_at":{"format":"date-time","type":"string"},"finished_at":{"format":"date-time","type":["string","null"]},"id":{"type":"string"},"idempotency_key":{"type":["string","null"],"description":"Immutable job-scoped Trident retry key."},"job_id":{"type":"string"},"kind":{"type":"string","enum":["original","rejudge","site_vetting"]},"purpose":{"type":"string","enum":["url_relevance","trident_summer_camp"]},"source_attempt_id":{"type":["string","null"],"description":"Immutable source crawl provenance for site_vetting."},"status":{"type":"string","enum":["queued","running","completed","partial","failed","cancelled"]}},"required":["attempt_id","baseline_evaluation_id","completion_reason","config","counts","created_at","finished_at","id","idempotency_key","job_id","kind","purpose","source_attempt_id","status"],"type":"object"},"FamilyApprovalSource":{"additionalProperties":false,"description":"The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","properties":{"conversationId":{"format":"uuid","type":"string"},"turnId":{"format":"uuid","type":"string"},"type":{"enum":["manual","assistant"]}},"required":["type"],"type":"object"},"FamilyArea":{"additionalProperties":false,"properties":{"label":{"maxLength":120,"minLength":1,"type":"string"},"postalCode":{"pattern":"^[0-9]{5}$","type":"string"},"region":{"pattern":"^[A-Z]{2}$","type":"string"}},"required":["label"],"type":"object"},"FamilyBudget":{"additionalProperties":false,"properties":{"amount":{"description":"At most two decimal places.","maximum":1000000,"minimum":0,"type":"number"},"currency":{"pattern":"^[A-Z]{3}$","type":"string"},"limit":{"enum":["hard_cap","preference"]},"period":{"enum":["day","week","month","session","season","year","total"]},"scope":{"enum":["per_child","household"]}},"required":["amount","currency","period","scope","limit"],"type":"object"},"FamilyCareRequirement":{"properties":{"childIds":{"description":"Only children the owner can currently access; revocation and deletion remove them.","items":{"format":"uuid","type":"string"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"details":{"additionalProperties":false,"properties":{"budget":{"$ref":"#/components/schemas/FamilyBudget"},"coverage":{"additionalProperties":false,"properties":{"minimumDaysPerWeek":{"maximum":7,"minimum":1,"type":"integer"},"mode":{"enum":["every_required_day","partial_ok"]}},"required":["mode"],"type":"object"},"dates":{"additionalProperties":false,"description":"Either an inclusive start/end range of at most one year, or 1-60 explicit dates.","properties":{"dates":{"items":{"format":"date","type":"string"},"maxItems":60,"type":"array"},"end":{"format":"date","type":"string"},"start":{"format":"date","type":"string"}},"type":"object"},"days":{"description":"Required for a range; not allowed with explicit dates.","items":{"enum":["mon","tue","wed","thu","fri","sat","sun"]},"minItems":1,"type":"array"},"exceptions":{"items":{"additionalProperties":false,"properties":{"date":{"format":"date","type":"string"},"hours":{"$ref":"#/components/schemas/FamilyTimeRange"},"needed":{"type":"boolean"}},"required":["date","needed"],"type":"object"},"maxItems":60,"type":"array"},"flexibility":{"additionalProperties":false,"properties":{"endMinutes":{"maximum":180,"minimum":0,"type":"integer"},"level":{"enum":["fixed","flexible"]},"startMinutes":{"maximum":180,"minimum":0,"type":"integer"}},"required":["level"],"type":"object"},"hours":{"$ref":"#/components/schemas/FamilyTimeRange"},"label":{"maxLength":80,"type":"string"},"recurrence":{"additionalProperties":false,"properties":{"intervalWeeks":{"enum":[1,2]}},"required":["intervalWeeks"],"type":"object"},"timezone":{"description":"IANA zone; defaults to the household time zone.","type":"string"},"travel":{"$ref":"#/components/schemas/FamilyTravel"}},"required":["dates","hours","flexibility","coverage"],"type":"object"},"id":{"format":"uuid","type":"string"},"provenance":{"additionalProperties":{"$ref":"#/components/schemas/FamilyProvenance"},"type":"object"},"updatedAt":{"format":"date-time","type":"string"},"version":{"type":"integer"}},"required":["id","version","childIds","details","provenance","createdAt","updatedAt"],"type":"object"},"FamilyCareRequirementInput":{"allOf":[{"additionalProperties":false,"properties":{"budget":{"$ref":"#/components/schemas/FamilyBudget"},"coverage":{"additionalProperties":false,"properties":{"minimumDaysPerWeek":{"maximum":7,"minimum":1,"type":"integer"},"mode":{"enum":["every_required_day","partial_ok"]}},"required":["mode"],"type":"object"},"dates":{"additionalProperties":false,"description":"Either an inclusive start/end range of at most one year, or 1-60 explicit dates.","properties":{"dates":{"items":{"format":"date","type":"string"},"maxItems":60,"type":"array"},"end":{"format":"date","type":"string"},"start":{"format":"date","type":"string"}},"type":"object"},"days":{"description":"Required for a range; not allowed with explicit dates.","items":{"enum":["mon","tue","wed","thu","fri","sat","sun"]},"minItems":1,"type":"array"},"exceptions":{"items":{"additionalProperties":false,"properties":{"date":{"format":"date","type":"string"},"hours":{"$ref":"#/components/schemas/FamilyTimeRange"},"needed":{"type":"boolean"}},"required":["date","needed"],"type":"object"},"maxItems":60,"type":"array"},"flexibility":{"additionalProperties":false,"properties":{"endMinutes":{"maximum":180,"minimum":0,"type":"integer"},"level":{"enum":["fixed","flexible"]},"startMinutes":{"maximum":180,"minimum":0,"type":"integer"}},"required":["level"],"type":"object"},"hours":{"$ref":"#/components/schemas/FamilyTimeRange"},"label":{"maxLength":80,"type":"string"},"recurrence":{"additionalProperties":false,"properties":{"intervalWeeks":{"enum":[1,2]}},"required":["intervalWeeks"],"type":"object"},"timezone":{"description":"IANA zone; defaults to the household time zone.","type":"string"},"travel":{"$ref":"#/components/schemas/FamilyTravel"}},"required":["dates","hours","flexibility","coverage"],"type":"object"},{"properties":{"childIds":{"items":{"format":"uuid","type":"string"},"maxItems":10,"minItems":1,"type":"array"},"source":{"$ref":"#/components/schemas/FamilyApprovalSource"}},"required":["childIds"],"type":"object"}]},"FamilyChildFields":{"additionalProperties":false,"description":"Optional registration fields. A planning label can identify a profile without full names; enabling registration reuse requires both child.firstName and child.lastName.","properties":{"child.allergies":{"maxLength":4000,"type":"string"},"child.birthDate":{"maxLength":10,"type":"string"},"child.firstName":{"maxLength":255,"type":"string"},"child.grade":{"maxLength":100,"type":"string"},"child.lastName":{"maxLength":255,"type":"string"},"child.notes":{"maxLength":4000,"type":"string"},"emergencyContact.name":{"maxLength":255,"type":"string"},"emergencyContact.phone":{"maxLength":64,"type":"string"},"profile.bio":{"maxLength":500,"type":"string"},"profile.color":{"description":"The child's accent: a design-system accent name such as purple or turquoise, never a raw color. The site owns the palette.","maxLength":32,"pattern":"^[a-z]+(-[a-z]+)*$","type":"string"},"profile.goals":{"maxLength":300,"type":"string"},"profile.location":{"description":"Where the child is, as the parent names it (a place label).","maxLength":120,"type":"string"},"profile.schedule":{"maxLength":300,"type":"string"}},"type":"object"},"FamilyChildInput":{"additionalProperties":false,"description":"A profile needs no name: a child may be saved before it is named, and readers then call it by its label, else its first name, else Child N. Registration reuse requires both names. PUT replaces fields and preferences; when planning is omitted, existing planning values are retained.","properties":{"fields":{"$ref":"#/components/schemas/FamilyChildFields"},"planning":{"$ref":"#/components/schemas/FamilyChildPlanning"},"preferences":{"$ref":"#/components/schemas/FamilyChildPreferences"},"reuseAllowed":{"default":false,"type":"boolean"},"source":{"$ref":"#/components/schemas/FamilyApprovalSource"},"version":{"minimum":1,"type":"integer"}},"required":["fields"],"type":"object"},"FamilyChildPlanning":{"additionalProperties":false,"description":"Enduring planning details registration forms do not use. A label alone can start a profile; registration reuse still needs first and last names. A date of birth always wins over an age: a save that gives both keeps the date of birth, and a date of birth replaces a saved age. Nothing is inferred.","properties":{"age":{"additionalProperties":false,"properties":{"asOf":{"format":"date","type":"string"},"years":{"maximum":21,"minimum":0,"type":"integer"}},"required":["years","asOf"],"type":"object","deprecated":true,"description":"Deprecated: send fields[\"child.birthDate\"] instead. Still accepted for older clients; ignored when a date of birth is given, and a profile with a date of birth has no age."},"gradeSchoolYear":{"description":"The school year fields[\"child.grade\"] applies to, such as 2026-2027.","pattern":"^[0-9]{4}-[0-9]{4}$","type":"string"},"label":{"maxLength":60,"type":"string"},"requirements":{"items":{"additionalProperties":false,"properties":{"category":{"enum":["health_safety","dietary","accessibility","supervision","communication","social","other"]},"detail":{"description":"A practical parent-stated condition; no diagnosis is needed.","maxLength":300,"minLength":1,"type":"string"},"kind":{"enum":["required","preference"]}},"required":["kind","category","detail"],"type":"object"},"maxItems":20,"type":"array"},"requirementsStatus":{"enum":["unknown","none","listed"]}},"type":"object"},"FamilyChildPreferences":{"additionalProperties":false,"properties":{"interests":{"items":{"maxLength":100,"type":"string"},"maxItems":20,"type":"array"},"interestsStatus":{"description":"unknown means the parent has not answered; none means no interests; listed requires interests. Omitted, it is listed when interests are present and otherwise unknown.","enum":["unknown","none","listed"]},"notes":{"maxLength":2000,"type":"string"}},"type":"object"},"FamilyChildProfile":{"properties":{"createdAt":{"format":"date-time","type":"string"},"fields":{"$ref":"#/components/schemas/FamilyChildFields"},"id":{"format":"uuid","type":"string"},"photoUrl":{"description":"The child's photo (GET /api/users/viewer/children/{childId}/photo?v=<hash>), served only to the child's owner and guardians; null when none. The URL changes when the photo does.","type":["string","null"]},"planning":{"$ref":"#/components/schemas/FamilyChildPlanning"},"preferences":{"$ref":"#/components/schemas/FamilyChildPreferences"},"provenance":{"additionalProperties":{"$ref":"#/components/schemas/FamilyProvenance"},"description":"Current-value approval for saved registration fields, preferences and planning values. Removed values leave no entry; unchanged values keep their provenance. Another adult's conversation reference is omitted.","type":"object"},"reuseAllowed":{"type":"boolean"},"role":{"description":"owner for the family's owner; guardian for a member (the per-child routes' word). familyRole says which member.","enum":["owner","guardian"],"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"minimum":1,"type":"integer"},"familyRole":{"$ref":"#/components/schemas/FamilyRole"}},"required":["id","version","fields","preferences","reuseAllowed","provenance","createdAt","updatedAt","role","planning","photoUrl","familyRole"],"type":"object","description":"A caregiver sees the care view: child.firstName, child.lastName, child.allergies, child.notes, emergencyContact.name, emergencyContact.phone and profile.color, the planning label and requirements, the photo; no preferences or provenance."},"FamilyCorrection":{"additionalProperties":false,"description":"Narrow correction: each named key replaces its value and null removes it. Unnamed values and their approvals are unchanged. The merged record is validated like a complete submission. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","properties":{"changes":{"minProperties":1,"type":"object"},"source":{"$ref":"#/components/schemas/FamilyApprovalSource"},"version":{"minimum":0,"type":"integer"}},"required":["version","changes"],"type":"object"},"FamilyHousehold":{"properties":{"preferences":{"$ref":"#/components/schemas/FamilyHouseholdPreferences"},"provenance":{"additionalProperties":{"$ref":"#/components/schemas/FamilyProvenance"},"type":"object"},"updatedAt":{"format":"date-time","type":["string","null"]},"version":{"description":"0 when nothing is saved.","type":"integer"},"referral":{"$ref":"#/components/schemas/FamilyAccountHouseholdReferral"}},"required":["version","preferences","provenance","updatedAt","referral"],"type":"object"},"FamilyHouseholdPreferences":{"additionalProperties":false,"properties":{"budget":{"$ref":"#/components/schemas/FamilyBudget"},"caregiverAvailability":{"additionalProperties":false,"description":"Recurring windows when a caregiver is available, date exceptions that replace them, and the times a caregiver can drop off or pick up.","properties":{"dropoff":{"$ref":"#/components/schemas/FamilyTimeRange"},"exceptions":{"items":{"additionalProperties":false,"properties":{"available":{"type":"boolean"},"date":{"format":"date","type":"string"},"hours":{"$ref":"#/components/schemas/FamilyTimeRange"}},"required":["date","available"],"type":"object"},"maxItems":60,"type":"array"},"pickup":{"$ref":"#/components/schemas/FamilyTimeRange"},"windows":{"items":{"additionalProperties":false,"properties":{"days":{"items":{"enum":["mon","tue","wed","thu","fri","sat","sun"]},"minItems":1,"type":"array"},"end":{"type":"string"},"start":{"type":"string"}},"required":["days","start","end"],"type":"object"},"maxItems":14,"type":"array"}},"type":"object"},"emergencyContacts":{"description":"Every distinct emergency contact of the family, including those its children point at. Index 0 is the primary contact; the rest are backups. Reorder by sending the whole list; a contact left out is removed, and children pointing at it lose their emergency contact.","items":{"$ref":"#/components/schemas/FamilyAccountEmergencyContact"},"maxItems":20,"type":"array"},"planningIntent":{"$ref":"#/components/schemas/FamilyAccountPlanningIntent"},"searchArea":{"$ref":"#/components/schemas/FamilyAccountSearchArea"},"timezone":{"description":"IANA time zone, such as America/Los_Angeles.","type":"string"},"travel":{"$ref":"#/components/schemas/FamilyTravel"},"home":{"$ref":"#/components/schemas/FamilyAccountLocation"},"places":{"type":"array","maxItems":10,"items":{"$ref":"#/components/schemas/FamilyAccountSavedPlace"},"description":"The household's saved places, in the parent's order. PATCH sends the whole list: an entry with an id is that place, one with a placeId is new, and a place left out is removed."}},"type":"object"},"FamilyMemoryFact":{"properties":{"category":{"enum":["activity_history","logistics","preference","other"]},"childId":{"format":"uuid","type":["string","null"]},"createdAt":{"format":"date-time","type":"string"},"expiresOn":{"format":"date","type":["string","null"]},"id":{"format":"uuid","type":"string"},"provenance":{"$ref":"#/components/schemas/FamilyProvenance"},"sourceReference":{"description":"Cleared when that conversation is deleted.","type":["object","null"]},"statement":{"maxLength":500,"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"version":{"type":"integer"}},"required":["id","version","childId","category","statement","expiresOn","provenance","sourceReference"],"type":"object"},"FamilyProvenance":{"description":"Approval of the current value only. Another adult's sourceReference is never returned; removed values leave no entry.","properties":{"actorId":{"format":"uuid","type":"string"},"approvedAt":{"format":"date-time","type":"string"},"source":{"enum":["manual","assistant"]},"sourceReference":{"properties":{"conversationId":{"format":"uuid","type":"string"},"turnId":{"format":"uuid","type":"string"}},"type":"object"},"version":{"description":"Record version at which this value was approved or reconfirmed.","type":"integer"}},"required":["source","actorId","approvedAt"],"type":"object"},"FamilyReconfirmation":{"additionalProperties":false,"description":"Records a fresh approval of unchanged current values and advances the record version. A key without a saved value returns 409.","properties":{"keys":{"items":{"type":"string"},"maxItems":40,"minItems":1,"type":"array"},"source":{"$ref":"#/components/schemas/FamilyApprovalSource"},"version":{"minimum":1,"type":"integer"}},"required":["version","keys"],"type":"object"},"FamilyTimeRange":{"additionalProperties":false,"properties":{"end":{"pattern":"^[0-2][0-9]:[0-5][0-9]$","type":"string"},"start":{"pattern":"^[0-2][0-9]:[0-5][0-9]$","type":"string"}},"required":["start","end"],"type":"object"},"FamilyTravel":{"additionalProperties":false,"description":"Needs maxMinutes or maxDistance. Without origin, travel is measured from the search area.","properties":{"limit":{"enum":["hard_cap","preference"]},"maxDistance":{"additionalProperties":false,"properties":{"unit":{"enum":["mi","km"]},"value":{"exclusiveMinimum":0,"maximum":500,"type":"number"}},"required":["value","unit"],"type":"object"},"maxMinutes":{"maximum":240,"minimum":1,"type":"integer"},"mode":{"enum":["driving","transit","walking","cycling"]},"origin":{"$ref":"#/components/schemas/FamilyArea"}},"required":["mode","limit"],"type":"object"},"FieldProvenanceEntry":{"additionalProperties":false,"description":"One provenance record shared by current sources, the latest observation, and decision history. Older source-only records may omit decision fields.","properties":{"action":{"enum":["keep","set","clear"],"type":"string"},"authority":{"enum":["automated","human"],"type":"string"},"batch_id":{"format":"uuid","type":"string"},"evidence":{"items":{"$ref":"#/components/schemas/FieldProvenanceEvidence"},"type":"array"},"field":{"description":"Canonical field or range endpoint described by this record.","type":"string"},"kind":{"enum":["scrape","manual_update","overseer","import","other"],"type":"string"},"label":{"description":"Fixed category label; submitter identities and personal batch names are omitted.","type":"string"},"observation":{"enum":["found","checked_not_found","not_applicable","failed","incomplete"],"type":"string"},"observed_at":{"format":"date-time","type":"string"},"reason":{"type":"string"},"recorded_at":{"description":"Contribution recording time, not source verification time.","format":"date-time","type":"string"},"review_id":{"format":"uuid","type":"string"},"review_version":{"minimum":1,"type":"integer"},"rule":{"type":"string"},"source_url":{"description":"Validated retained website-scrape root. The root does not cite an individual field.","format":"uri","type":"string"},"source_url_scope":{"enum":["site"],"type":"string"}},"required":["kind","label"],"type":"object"},"FieldProvenanceEvidence":{"additionalProperties":false,"properties":{"captured_at":{"format":"date-time","type":"string"},"quote":{"type":"string"},"url":{"description":"Validated HTTP(S) field citation or captured-page URL, without userinfo, query, or fragment.","format":"uri","type":"string"}},"required":["url"],"type":"object"},"GeocodingCoverage":{"properties":{"affectedRecords":{"description":"Live entity references to locations in scope.","minimum":0,"type":"integer"},"applicableLocations":{"description":"Locations eligible for US reference derivation.","minimum":0,"type":"integer"},"missingBoundaries":{"description":"Approximate locations lacking a boundary.","minimum":0,"type":"integer"},"missingCoordinates":{"description":"Locations lacking a complete pair.","minimum":0,"type":"integer"},"pending":{"description":"Locations awaiting automatic work.","minimum":0,"type":"integer"},"precision":{"additionalProperties":{"minimum":0,"type":"integer"},"type":"object"},"reference":{"properties":{"available":{"type":"boolean"},"source":{"type":"string"},"version":{"type":"string"}},"type":"object"},"scope":{"properties":{"city":{"description":"Optional recorded address label, not a city polygon.","type":"string"},"postalCode":{"description":"Optional US ZIP or ZIP+4; normalized to five digits.","type":"string"},"state":{"description":"US state name or code; normalized to the two-letter code.","type":"string"}},"type":"object"},"totalLocations":{"description":"Distinct live configurations in scope.","minimum":0,"type":"integer"},"unresolved":{"description":"Locations with unresolved automatic derivations.","minimum":0,"type":"integer"},"worker":{"properties":{"error":{"type":"string"},"geocodioConfigured":{"type":"boolean"},"lastSeenAt":{"format":"date-time","type":"string"},"status":{"type":"string"}},"type":"object"},"zipMatched":{"description":"Locations matched to ZIP reference data.","minimum":0,"type":"integer"},"zipPopulated":{"description":"Locations with a populated ZIP.","minimum":0,"type":"integer"},"zipValid":{"description":"Locations with a recognized valid ZIP.","minimum":0,"type":"integer"}},"type":"object"},"GeocodingItem":{"properties":{"address":{"description":"Staff-only snapshot of the requested address.","type":"string"},"configurationId":{"format":"uuid","type":"string"},"error":{"type":"string"},"id":{"format":"uuid","type":"string"},"precision":{"$ref":"#/components/schemas/CoordinatePrecision"},"status":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","configurationId","status","address","updatedAt"],"type":"object"},"GeocodingJob":{"allOf":[{"$ref":"#/components/schemas/GeocodingPreview"},{"properties":{"cancelled":{"description":"Items cancelled before submission.","minimum":0,"type":"integer"},"completedAt":{"format":"date-time","type":"string"},"createdAt":{"format":"date-time","type":"string"},"error":{"type":"string"},"failed":{"description":"Failed items.","minimum":0,"type":"integer"},"id":{"format":"uuid","type":"string"},"processed":{"description":"Items with settled outcomes.","minimum":0,"type":"integer"},"requestedBy":{"type":"string"},"stale":{"description":"Items changed since the confirmed snapshot.","minimum":0,"type":"integer"},"startedAt":{"format":"date-time","type":"string"},"status":{"description":"Persisted job lifecycle status. Uncertain provider submissions require reconciliation before replacement work.","type":"string"},"submittedLookups":{"description":"Provider lookups already submitted against the frozen budget.","minimum":0,"type":"integer"},"succeeded":{"description":"Items with accepted coordinates.","minimum":0,"type":"integer"},"unresolved":{"description":"Items without an acceptable result.","minimum":0,"type":"integer"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","status","requestedBy","createdAt","updatedAt","submittedLookups","processed","succeeded","unresolved","failed","stale","cancelled"],"type":"object"}]},"GeocodingPreview":{"allOf":[{"$ref":"#/components/schemas/GeocodingPreviewInput"},{"properties":{"affectedRecords":{"description":"Live organizations, activities, and sessions referencing the eligible configurations.","minimum":0,"type":"integer"},"configurations":{"description":"Distinct eligible live configurations. Shared configurations count once.","minimum":0,"type":"integer"},"estimatedCostMicrousd":{"description":"Estimated cost in millionths of a US dollar, before shared free credits.","minimum":0,"type":"integer"},"exclusions":{"additionalProperties":{"minimum":0,"type":"integer"},"description":"Excluded location counts by reason.","type":"object"},"fingerprint":{"description":"Quote fingerprint; must match recomputed eligibility and pricing at confirmation.","type":"string"},"requiredLookups":{"description":"Maximum new provider lookups covered by this quote.","minimum":0,"type":"integer"},"reusedAddresses":{"description":"Addresses with reusable successful results in gap mode.","minimum":0,"type":"integer"},"uniqueAddresses":{"description":"Distinct normalized eligible addresses.","minimum":0,"type":"integer"},"unitPriceMicrousd":{"description":"Price in millionths of a US dollar per lookup. Initially 1000 ($1 per 1000 lookups).","minimum":0,"type":"integer"}},"required":["fingerprint","configurations","affectedRecords","uniqueAddresses","reusedAddresses","requiredLookups","unitPriceMicrousd","estimatedCostMicrousd","exclusions"],"type":"object"}]},"GeocodingPreviewInput":{"properties":{"mode":{"default":"gaps","description":"gaps preserves existing address-level coordinates; refresh also refreshes previous Geocodio results, preserving source coordinates.","enum":["gaps","refresh"],"type":"string"},"rerunOf":{"description":"Historical parent job. Reruns create a new job; the parent remains unchanged.","format":"uuid","type":"string"},"scope":{"$ref":"#/components/schemas/GeocodingScope"}},"required":["scope"],"type":"object"},"GeocodingScope":{"properties":{"city":{"description":"Optional recorded address label, not a city polygon.","type":"string"},"postalCode":{"description":"Optional US ZIP or ZIP+4; normalized to five digits.","type":"string"},"state":{"description":"US state name or code; normalized to the two-letter code.","type":"string"}},"required":["state"],"type":"object"},"LocationFilter":{"oneOf":[{"additionalProperties":false,"properties":{"latitude":{"maximum":90,"minimum":-90,"type":"number"},"longitude":{"maximum":180,"minimum":-180,"type":"number"},"radiusMeters":{"exclusiveMinimum":0,"type":"number"},"type":{"const":"point"}},"required":["type","latitude","longitude","radiusMeters"],"type":"object"},{"additionalProperties":false,"properties":{"code":{"description":"Two-letter US state code or five-digit ZIP matching kind. Normalized to uppercase. An active boundary must exist.","type":"string"},"kind":{"enum":["state","zip"],"type":"string"},"radiusMeters":{"default":0,"description":"Distance beyond the area boundary, in meters.","minimum":0,"type":"number"},"type":{"const":"area"}},"required":["type","kind","code"],"type":"object"}]},"LocationMatch":{"description":"Only available when location is supplied. ZIP area matches are possible, not confirmed venue locations. State coverage is broad. Missing coordinates/boundaries stay unresolved.","enum":["point_match","possible_area_match","state_coverage","no_match","unresolved"],"type":"string"},"LocationQueryMetadata":{"properties":{"boundaryDatasets":{"description":"Available active and retained reference versions; not a per-row list of sources used. Preserve Census release/ZCTA vintage attribution.","items":{"properties":{"source":{"type":"string"},"version":{"type":"string"}},"required":["version","source"],"type":"object"},"type":"array"},"filter":{"$ref":"#/components/schemas/LocationFilter"},"pointCoordinates":{"description":"Current responses use source for full stored precision of disclosed coordinates. rounded_to_two_decimal_places remains readable in historical saved metadata. Source quality is described separately.","enum":["source","rounded_to_two_decimal_places"],"type":"string"}},"required":["filter","boundaryDatasets","pointCoordinates"],"type":"object"},"OverseerEvent":{"additionalProperties":false,"properties":{"actorId":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"details":{"description":"Bounded audit summary, state, scheduling information or linked records. Excludes checkpoints, credentials and lease authority. Canonical listing links in details.links include current display names, including historical events. Unnamed or unavailable records receive descriptive labels without raw IDs. Campaign proposal links include campaignId and their exact version; historical references never silently select a newer head. listing_feedback carries structured listing kind/id, label and feedback separately from proposal decisions. Labels do not change stored audit records or action receipts. Completed assistant messages use kind=agent_output with details.text, source (coordinator or subagent), phase (commentary or final_answer), optional ordinal agentLabel, and truncated. Text preserves line breaks after credential redaction and byte bounds. Coordinator final control documents, reasoning, tool payloads and native identities are excluded. These observations do not replace verified progress or directive state; existing history pagination applies. One-off requests use user_message with requestId and text; message_delivered and message_cancelled acknowledge that requestId. A delivery acknowledgment does not establish requested work completion. Global pause transitions use kind=pause_changed with details.paused and details.pauseVersion. They preserve the saved instruction revision.","properties":{"agentLabel":{"description":"Optional ordinal label scoped to an execution; no native identifier.","pattern":"^Subagent [1-9][0-9]*$","type":"string"},"links":{"items":{"properties":{"id":{"type":"string"},"kind":{"type":"string"},"label":{"description":"Current listing name, exact proposal label, or descriptive unavailable fallback.","type":"string"},"campaignId":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1},"referenceIds":{"type":"array","items":{"type":"string"}}},"type":"object"},"type":"array"},"phase":{"description":"Coordinator events contain commentary only.","enum":["commentary","final_answer"]},"source":{"enum":["coordinator","subagent"]},"text":{"description":"Staff request text for user_message, or credential-redacted assistant text for agent_output. Assistant text preserves whitespace; empty only for an explicit truncation marker.","type":"string"},"truncated":{"description":"The recorded message omits output due to an observation bound.","type":"boolean"},"requestId":{"type":"string","format":"uuid","description":"One-off request identity on user_message, message_delivered and message_cancelled events."},"paused":{"type":"boolean","description":"New desired pause state on pause_changed events."},"pauseVersion":{"type":"integer","minimum":0,"description":"Version of the pause_changed control transition."}},"type":"object"},"directive":{"description":"Actual saved text from this event revision. Present only for directive_saved, including an empty string for idle.","type":"string"},"id":{"minimum":1,"type":"integer"},"kind":{"type":"string"},"revision":{"minimum":0,"type":"integer"}},"required":["id","revision","actorId","kind","details","createdAt"],"type":"object"},"OverseerEvidenceCapture":{"properties":{"capturedAt":{"format":"date-time","type":"string"},"deviceScale":{"const":1},"documentHeight":{"type":"integer"},"documentWidth":{"type":"integer"},"finalUrl":{"type":"string"},"height":{"type":"integer"},"id":{"format":"uuid","type":"string"},"imageDeletedAt":{"description":"When image bytes were removed. Source metadata and update history remain available.","format":"date-time","type":"string"},"pdfPage":{"type":"integer"},"pdfPageCount":{"type":"integer"},"rendererVersion":{"type":"string"},"scrollY":{"type":"integer"},"sha256":{"type":"string"},"sourceKind":{"enum":["webpage","pdf"]},"sourceSha256":{"type":"string"},"title":{"type":"string"},"url":{"type":"string"},"viewportHeight":{"type":"integer"},"viewportWidth":{"type":"integer"},"warnings":{"description":"Known rendering limitations that reviewers must see next to the original screenshot. Missing source images or fallback fonts can affect interpretation.","items":{"enum":["visible_images_unavailable","fonts_unavailable"],"type":"string"},"type":"array"},"width":{"type":"integer"}},"required":["id","url","finalUrl","capturedAt","sha256","width","height","viewportWidth","viewportHeight","sourceKind","rendererVersion","scrollY","documentWidth","documentHeight","deviceScale"],"type":"object"},"OverseerEvidenceRectangle":{"properties":{"height":{"minimum":1,"type":"integer"},"width":{"minimum":1,"type":"integer"},"x":{"minimum":0,"type":"integer"},"y":{"minimum":0,"type":"integer"}},"required":["x","y","width","height"],"type":"object"},"OverseerLinkedJob":{"additionalProperties":false,"properties":{"campId":{"format":"uuid","type":["string","null"]},"createdAt":{"format":"date-time","type":"string"},"id":{"type":"string"},"kind":{"enum":["crawler_job","scraper_job"],"type":"string"},"label":{"type":"string"},"linkedAt":{"format":"date-time","type":"string"},"revision":{"description":"First durable link revision.","minimum":1,"type":"integer"},"status":{"description":"Current status from the actual job queue.","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["kind","id","revision","label","status","campId","createdAt","updatedAt","linkedAt"],"type":"object"},"OverseerRejectedReviewDecision":{"description":"A durable rejection receipt for exactly this normalized request body and UUID. It is returned only after the rejection has committed. It does not count as a reviewer decision or change canonical records.","properties":{"data":{"type":"null"},"decisionOutcome":{"const":"rejected"},"error":{"const":true},"message":{"type":"string"},"requestId":{"format":"uuid","type":"string"},"success":{"const":false}},"required":["success","error","data","message","requestId","decisionOutcome"],"type":"object"},"OverseerReview":{"properties":{"activityId":{"format":"uuid","type":"string"},"actorId":{"format":"uuid","type":"string"},"appliedResult":{"type":["object","null"],"description":"Immutable application receipt with actual record values, changed fields, and contribution batch identity."},"captures":{"items":{"$ref":"#/components/schemas/OverseerEvidenceCapture"},"type":"array"},"checkedNotFound":{"type":"array","items":{"type":"string"},"description":"Fields explicitly researched without a usable value. Empty research results use this same conservative state; existing canonical values are preserved. Omitted on historical reviews without recorded checks."},"cleared":{"items":{"type":"string"},"type":"array"},"createdAt":{"format":"date-time","type":"string"},"entityId":{"format":"uuid","type":"string"},"evidence":{"items":{"$ref":"#/components/schemas/OverseerReviewEvidence"},"type":"array"},"expected":{"type":"object"},"media":{"$ref":"#/components/schemas/OverseerMediaContribution","description":"Submitted media contribution; the applied result records its actual effect."},"expectedMedia":{"$ref":"#/components/schemas/OverseerMediaProjection","description":"The entity's media when the proposal was made. Omitted when the proposal changes no media."},"mediaInspections":{"type":"array","items":{"$ref":"#/components/schemas/OverseerMediaInspection"},"description":"Omitted when no source image was inspected."},"gapId":{"type":"string","description":"The scraper gap this update addresses; retained in its contribution origin."},"feedback":{"items":{"$ref":"#/components/schemas/OverseerReviewFeedback"},"type":"array"},"feedbackId":{"description":"Latest correction feedback ID for this version. A corrective submission must match the observed feedback identity.","minimum":0,"type":"integer"},"fields":{"type":"object"},"id":{"format":"uuid","type":"string"},"kind":{"enum":["organization","activity","session"]},"label":{"type":"string"},"operation":{"enum":["create","update","remove"]},"organizationId":{"format":"uuid","type":"string"},"originActionId":{"type":"string"},"originRevision":{"type":"integer"},"proposalCreatedAt":{"description":"Creation time of this immutable update version; createdAt is the identity creation time.","format":"date-time","type":"string"},"removeRelevantUrls":{"items":{"type":"string"},"type":"array"},"status":{"enum":["pending_approval","denied","applied","not_applied","revalidation_requested","reverting","reverted","revert_failed"]},"updatedAt":{"format":"date-time","type":"string"},"version":{"minimum":1,"type":"integer"},"auditStatus":{"enum":["unreviewed","reviewed","changes_requested"]},"batchId":{"type":"string","format":"uuid"},"appliedAt":{"type":"string","format":"date-time"},"suppressed":{"type":"boolean"},"revalidationReason":{"type":"string"},"latestVersion":{"type":"integer","minimum":1},"removal":{"$ref":"#/components/schemas/OverseerReviewRemoval","description":"Present only for operation=remove. Saving a proposal leaves canonical records live until explicit staff approval."},"provisionalEntityId":{"type":"string","format":"uuid"},"campaignId":{"type":"string","format":"uuid","description":"Campaign which published this historical version, when it originated in a campaign. Request corrections through an explicit targeted campaign."}},"required":["id","version","kind","organizationId","operation","status","label","originRevision","originActionId","actorId","fields","cleared","removeRelevantUrls","expected","evidence","captures","feedback","createdAt","updatedAt","proposalCreatedAt","auditStatus","suppressed","latestVersion"],"type":"object"},"OverseerMediaSource":{"type":"object","additionalProperties":false,"required":["kind"],"description":"Where a media contribution came from.","properties":{"kind":{"type":"string","enum":["trident","manual","legacy","upload"]},"website":{"type":"string","description":"The website a Trident or manual contribution was read from."},"record_id":{"type":"string","description":"The source record a legacy or Trident contribution belongs to."}}},"OverseerMediaItem":{"type":"object","additionalProperties":false,"required":["url","label","kind","placement"],"description":"One image a contribution adds.","properties":{"url":{"type":"string","description":"Inspected source URL, or final immutable R2 URL from a verified stock import receipt."},"label":{"type":"string"},"kind":{"type":"string"},"placement":{"type":"string","enum":["gallery","logo"],"description":"Where the image is shown: the organization or activity gallery, or the organization logo."},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1},"source_page_url":{"type":"string"},"observed_at":{"type":"string","format":"date-time"},"stock":{"$ref":"#/components/schemas/OverseerStockImage"}}},"OverseerMediaRemoval":{"type":"object","additionalProperties":false,"required":["media_id","contribution_ids"],"description":"A media member to remove, with the contributions observed for it.","properties":{"media_id":{"type":"string"},"contribution_ids":{"type":"array","items":{"type":"string"}},"baseline":{"type":"boolean","description":"Present and true when the member came from the baseline rather than a contribution."}}},"OverseerMediaContribution":{"type":"object","additionalProperties":false,"required":["mode","source","status","items"],"description":"Typed media changes a proposal makes.","properties":{"mode":{"type":"string","enum":["snapshot","delta"]},"source":{"$ref":"#/components/schemas/OverseerMediaSource"},"status":{"type":"string","enum":["complete","incomplete"]},"items":{"type":"array","items":{"$ref":"#/components/schemas/OverseerMediaItem"}},"remove":{"type":"array","items":{"$ref":"#/components/schemas/OverseerMediaRemoval"},"description":"Omitted when nothing is removed."},"cover_id":{"type":"string","description":"The member to use as the cover. Empty resets automatic selection; omitted leaves it unchanged."},"logo_id":{"type":"string","description":"The member to use as the logo. Empty resets automatic selection; omitted leaves it unchanged."}}},"OverseerMediaResource":{"type":"object","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string"},"remote_url":{"type":"string"},"uploaded_asset_id":{"type":"string"},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1},"stock":{"$ref":"#/components/schemas/OverseerStockImage"}}},"OverseerMediaProvenance":{"type":"object","additionalProperties":false,"required":["source"],"properties":{"contribution_id":{"type":"string"},"source":{"$ref":"#/components/schemas/OverseerMediaSource"},"source_page_url":{"type":"string"},"observed_at":{"type":"string","format":"date-time"},"baseline":{"type":"boolean"}}},"OverseerMediaMember":{"type":"object","additionalProperties":false,"required":["resource","placement","position","label","kind","provenance"],"properties":{"resource":{"$ref":"#/components/schemas/OverseerMediaResource"},"placement":{"type":"string","enum":["gallery","logo"],"description":"Where the image is shown: the organization or activity gallery, or the organization logo."},"position":{"type":"integer","minimum":0},"label":{"type":"string"},"kind":{"type":"string"},"provenance":{"type":"array","items":{"$ref":"#/components/schemas/OverseerMediaProvenance"}}}},"OverseerMediaProjection":{"type":"object","additionalProperties":false,"required":["members","cover_id","logo_id","cover_explicit","logo_explicit"],"description":"The media an entity shows: its members and the cover and logo chosen among them.","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/OverseerMediaMember"}},"cover_id":{"type":"string"},"logo_id":{"type":"string"},"cover_explicit":{"type":"boolean"},"logo_explicit":{"type":"boolean"}}},"OverseerMediaInspection":{"type":"object","additionalProperties":false,"required":["itemIndex","actionId","candidateId"],"description":"The source-image inspection or stock import receipt that justified adding one media item.","properties":{"itemIndex":{"type":"integer","minimum":0,"description":"Index into media.items."},"actionId":{"type":"string"},"candidateId":{"type":"string"},"mediaId":{"type":"string"}}},"OverseerReviewDecisionInput":{"additionalProperties":false,"properties":{"action":{"enum":["mark_reviewed","request_changes","revert","approve","deny","retry"],"description":"retry explicitly resumes automatic updates only for the latest, suppressed, fully reverted campaign-published version. It preserves reviewed audit status and does not enqueue correction work."},"expectedVersion":{"minimum":1,"type":"integer"},"feedback":{"description":"Required nonempty text only for request_changes, at most 8000 UTF-8 bytes. Other actions reject nonempty feedback.","type":"string"},"requestId":{"format":"uuid","type":"string"}},"required":["expectedVersion","requestId","action"],"type":"object"},"OverseerReviewDetail":{"properties":{"current":{"type":"object"},"currentMissing":{"description":"The current canonical record is unavailable. A retained removed row remains readable and is not treated as missing; immutable history and evidence remain readable.","type":"boolean"},"form":{"type":"object","description":"Editable form for ordinary researched changes; an empty object for removal proposals."},"proposed":{"type":"object","description":"Actual immutable applied record for ordinary applied versions; submitted values for ordinary unapplied versions. Removal proposals use their retained root baseline and removal snapshot rather than editable field differences."},"related":{"properties":{"activity":{"description":"Existing parent activity, or an id/missing marker if unavailable.","type":"object"},"hasMoreReviews":{"type":"boolean"},"organization":{"description":"Existing parent organization, or an id/missing marker if unavailable.","type":"object"},"reviews":{"items":{"$ref":"#/components/schemas/OverseerReviewSummary"},"type":"array"}},"required":["organization","reviews","hasMoreReviews"],"type":"object"},"review":{"$ref":"#/components/schemas/OverseerReview"}},"required":["review","form","current","proposed","currentMissing","related"],"type":"object"},"OverseerReviewEvidence":{"properties":{"field":{"type":"string"},"quote":{"type":"string"},"screenshots":{"items":{"properties":{"captureId":{"format":"uuid","type":"string"},"rectangle":{"$ref":"#/components/schemas/OverseerEvidenceRectangle"}},"required":["captureId","rectangle"],"type":"object"},"type":"array"},"url":{"format":"uri","type":"string"},"valuePointer":{"description":"JSON pointer to the normalized proposed value or check: /fields/name, /fields/relevant_urls/0, /checkedNotFound/0, /cleared/0, /removeRelevantUrls/0, /organizationId, or /activityId, or /removal/reason for removal evidence with field=removal.","type":"string"}},"required":["field","valuePointer","url","quote","screenshots"],"type":"object"},"OverseerReviewFeedback":{"properties":{"action":{"type":"string","description":"Recorded decision action. approve and deny decide pending removal proposals; historical action labels remain unchanged. retry records explicit resumption of automatic updates for a fully reverted campaign version without enqueuing research."},"actorId":{"format":"uuid","type":"string"},"createdAt":{"format":"date-time","type":"string"},"feedback":{"type":"string"},"id":{"type":"integer"},"requestId":{"description":"The caller-generated decision request ID; use it to reconcile an uncertain browser response without resubmitting.","format":"uuid","type":"string"},"version":{"type":"integer"}},"required":["id","version","actorId","action","feedback","createdAt","requestId"],"type":"object"},"OverseerReviewGroup":{"type":"object","properties":{"organizationId":{"type":"string","format":"uuid"},"label":{"type":"string"},"updateCount":{"type":"integer","minimum":0},"counts":{"$ref":"#/components/schemas/OverseerReviewKindCounts"},"unreviewedCount":{"type":"integer","minimum":0},"changesRequestedCount":{"type":"integer","minimum":0},"newestAt":{"type":"string","format":"date-time"}},"required":["organizationId","label","updateCount","counts","unreviewedCount","changesRequestedCount","newestAt"]},"OverseerReviewGroupsPage":{"type":"object","properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/OverseerReviewGroup"}},"nextAfterId":{"type":"string"},"hasMore":{"type":"boolean"}},"required":["groups","hasMore"]},"OverseerReviewKindCounts":{"properties":{"activity":{"description":"Number of activity proposals.","minimum":0,"type":"integer"},"organization":{"description":"Number of organization proposals.","minimum":0,"type":"integer"},"session":{"description":"Number of session proposals.","minimum":0,"type":"integer"}},"required":["organization","activity","session"],"type":"object"},"OverseerReviewPage":{"properties":{"counts":{"type":"object","properties":{"applied":{"type":"integer","minimum":0},"not_applied":{"type":"integer","minimum":0},"revalidation_requested":{"type":"integer","minimum":0},"reverting":{"type":"integer","minimum":0},"reverted":{"type":"integer","minimum":0},"revert_failed":{"type":"integer","minimum":0},"pending_approval":{"type":"integer","minimum":0},"denied":{"type":"integer","minimum":0}},"description":"Counts by application status across the entire matching cohort before pagination when entityKind/entityId or retainedOnly=true is supplied; otherwise global version history counts."},"hasMore":{"type":"boolean"},"nextAfterId":{"type":"string"},"reviews":{"items":{"$ref":"#/components/schemas/OverseerReview"},"type":"array"}},"required":["reviews","hasMore","counts"],"type":"object"},"OverseerSlackDelivery":{"additionalProperties":false,"properties":{"createdAt":{"format":"date-time","type":"string"},"deliveredAt":{"format":"date-time","type":["string","null"]},"errorSummary":{"description":"Fixed safe delivery summary; never raw provider or saved error text.","type":["string","null"]},"id":{"minimum":1,"type":"integer"},"kind":{"type":"string"},"revision":{"minimum":1,"type":"integer"},"state":{"enum":["pending","delivering","delivered","failed","cancelled"],"type":"string"}},"required":["id","revision","kind","state","createdAt","deliveredAt","errorSummary"],"type":"object"},"OverseerSnapshot":{"additionalProperties":false,"properties":{"actorId":{"description":"User who last saved the current directive or cleared its memory, or empty before the first save.","type":"string"},"createdAt":{"format":"date-time","type":"string"},"directive":{"type":"string","description":"Compatibility projection containing only the enabled standing instruction text. Use directives when editing."},"executionActive":{"description":"True only when the current revision is running and its worker lease has not expired at response time. A saved but unclaimed directive, absent/expired lease, and execution belonging to a superseded revision return false. Does not establish process health or worker configuration. Always false while paused, including while a revoked native process is finishing cleanup.","type":"boolean"},"latestSlackDelivery":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OverseerSlackDelivery"}]},"leaseExpiresAt":{"description":"Expiry of the current revision worker lease, or null if not claimed. May be in the past; executionActive is then false. Contains no authority token.","format":"date-time","type":["string","null"]},"nextReportAt":{"format":"date-time","type":["string","null"]},"nextWakeAt":{"format":"date-time","type":["string","null"]},"reportCadenceSeconds":{"description":"Worker-interpreted report cadence. Zero disables scheduled reports.","minimum":0,"type":"integer"},"revision":{"minimum":0,"type":"integer"},"state":{"enum":["idle","running","waiting","needs_rewording","satisfied","blocked"],"type":"string"},"subagentCount":{"description":"Last-reported pending or running direct subagents in the current execution. Reports can lag actual activity and exclude nested children. Null while execution is active without valid telemetry; zero when executionActive is false. This is not an instantaneous total or cumulative spawned count.","minimum":0,"type":["integer","null"]},"subagentObservedAt":{"description":"Time the worker recorded the latest valid direct-subagent aggregate. Null when its count is unavailable or executionActive is false. A fresh timestamp does not establish that every child reported a new state.","format":"date-time","type":["string","null"]},"summary":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"directives":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/OverseerDirective"}},"disabledTools":{"type":"array","uniqueItems":true,"items":{"type":"string"},"description":"Disabled names from availableTools. Mandatory history, review reconciliation and lifecycle tools cannot be disabled."},"availableTools":{"type":"array","items":{"$ref":"#/components/schemas/OverseerToolOption"}},"pendingMessages":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/OverseerMessage"},"description":"Undelivered one-off requests for the current revision in submission order. A message being processed remains queued until its turn finishes successfully."},"paused":{"type":"boolean","description":"Global operator pause, independent of instruction revision and underlying work state. Prevents new agent turns and revokes current turn authority without clearing memory, settings or queued requests."},"pauseVersion":{"type":"integer","minimum":0,"description":"Monotonic version for explicit pause/resume changes. Compare with expectedPauseVersion; unchanged desired state with matching versions is a no-op."}},"required":["revision","directive","state","actorId","summary","nextWakeAt","reportCadenceSeconds","nextReportAt","createdAt","updatedAt","executionActive","leaseExpiresAt","latestSlackDelivery","subagentCount","subagentObservedAt","directives","disabledTools","availableTools","pendingMessages","paused","pauseVersion"],"type":"object"},"SessionScheduleConfiguration":{"additionalProperties":false,"description":"One attendance group sharing session and care hours within an enrollment. Exactly one of explicit dates, a bounded weekly date_rule, or a date_range envelope is required. Effective attendance dates must not overlap another configuration. Aggregate weekly-rule expansion is bounded by safety policy (currently 10,000 effective dates per collection); explicit-date arrays do not consume the limit, and excess is rejected without truncation. Preserve the canonical rule rather than replacing it with expanded dates. A date_range states the enrollment's bounds and claims no attended day; it needs at least one known date, hour or care fact. Care prices can vary between configurations.","oneOf":[{"required":["dates"],"not":{"anyOf":[{"required":["date_rule"]},{"required":["date_range"]}]}},{"required":["date_rule"],"not":{"anyOf":[{"required":["dates"]},{"required":["date_range"]}]}},{"required":["date_range"],"not":{"anyOf":[{"required":["dates"]},{"required":["date_rule"]}]}}],"properties":{"after_care_available":{"description":"False explicitly rules out after care; null means unknown. False cannot carry a care clock or quote.","type":["boolean","null"]},"after_care_end_time":{"description":"Local 24-hour clock in the session time zone; null means unknown.","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","type":["string","null"]},"after_care_pricing":{"$ref":"#/components/schemas/CarePricing"},"before_care_available":{"description":"False explicitly rules out before care; null means unknown. False cannot carry a care clock or quote.","type":["boolean","null"]},"before_care_pricing":{"$ref":"#/components/schemas/CarePricing"},"before_care_start_time":{"description":"Local 24-hour clock in the session time zone; null means unknown.","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","type":["string","null"]},"date_range":{"$ref":"#/components/schemas/SessionScheduleDateRange"},"date_rule":{"$ref":"#/components/schemas/SessionScheduleDateRule"},"dates":{"description":"Explicit attendance dates, possibly nonconsecutive; never inferred from a date envelope.","items":{"description":"An explicitly stated attendance date, YYYY-MM-DD.","format":"date","type":"string"},"minItems":1,"type":"array","uniqueItems":true},"end_time":{"description":"Local 24-hour clock in the session time zone; null means unknown.","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","type":["string","null"]},"start_time":{"description":"Local 24-hour clock in the session time zone; null means unknown.","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","type":["string","null"]},"time_kind":{"description":"How start_time and end_time read: daily attendance hours (end after start each day), arrival_departure (arrival on the first day, departure on the last, which may be earlier in the day), or unspecified. Omitted means daily, or unspecified for a date_range. Care clocks are checked against daily hours only.","type":"string","enum":["daily","arrival_departure","unspecified"]}},"type":"object"},"SessionScheduleDateRule":{"additionalProperties":false,"description":"Source-supported weekly attendance within inclusive local calendar bounds. The inclusive bounds differ by at most 3660 days and at least one effective attendance date must remain. Bounds alone never imply weekdays or holiday closures.","properties":{"days_of_week":{"description":"Unique explicitly stated weekdays, normalized in Monday-to-Sunday order.","items":{"enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"end_date":{"description":"Inclusive last local date of the stated rule, YYYY-MM-DD; must not precede start_date.","format":"date","type":"string"},"excluded_dates":{"description":"Optional unique closure dates. Omitted or null means no exclusions; exclusions cannot remove every effective date.","items":{"description":"A source-stated closure on a selected weekday within the inclusive bounds, YYYY-MM-DD.","format":"date","type":"string"},"type":["array","null"],"uniqueItems":true},"start_date":{"description":"Inclusive first local date of the stated rule, YYYY-MM-DD.","format":"date","type":"string"}},"required":["start_date","end_date","days_of_week"],"type":"object"},"BookingError":{"type":"object","required":["error","message"],"properties":{"error":{"const":true},"message":{"type":"string","description":"A sentence you can show the parent as is."},"data":{"type":"null"}}},"BookingMoney":{"type":"object","required":["amountCents","currency"],"properties":{"amountCents":{"type":"integer","description":"Amount in the smallest currency unit, such as cents."},"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"ISO 4217 currency code."}},"example":{"amountCents":45000,"currency":"USD"}},"BookingStatus":{"type":"string","enum":["authorizing","prepared","submitting","submit_uncertain","accepted","running","awaiting_decision","applied","confirmed","waitlisted","failed","cancelled","uncertain"],"description":"The step the booking is at, for support and diagnostics. Apps decide what to show from `phase`; each status belongs to exactly one phase, and a new status may be added to an existing phase without notice.","x-open-enum":true},"BookingPaymentStatus":{"type":"string","enum":["none","hold_pending","held","captured","released","failed"],"description":"The state of the hold on the parent’s card. `hold_pending` means a hold attempt’s outcome is unknown; it’s retried with the same idempotency key."},"BookingShareableField":{"type":"string","enum":["child.birthDate","child.grade","child.allergies","guardian.phone"],"description":"Optional fields the parent can choose to share. The child’s first and last name and the guardian’s name and email are always shared. child.* fields come from the child’s profile; guardian.phone is the contact phone on the account of the adult booking (the owner or a co-parent), the phone their profile shows; without one, sharing it is refused."},"BookingAlwaysSharedField":{"type":"string","enum":["child.firstName","child.lastName","guardian.email","guardian.name"],"description":"Fields every booking shares: the child's names from their profile, and the guardian's name and email from the account of the adult booking (the owner or a co-parent), never the child's profile. A booking cannot be made without them."},"FamilyBookingForm":{"type":"object","additionalProperties":false,"required":["sessionId","alwaysShared","shareFields"],"description":"Profile-sharing choices used by family booking approval. These remain uniform across sessions; the canonical session booking_fields describes provider requirements separately and does not authorize sharing or execution.","properties":{"sessionId":{"type":"string","format":"uuid"},"alwaysShared":{"type":"array","items":{"$ref":"#/components/schemas/BookingAlwaysSharedField"}},"shareFields":{"type":"array","items":{"$ref":"#/components/schemas/BookingShareableField"}}}},"BookingApprovalInput":{"type":"object","additionalProperties":false,"required":["idempotencyKey","childProfileId","childProfileVersion","sessionId","maxSpend","allowWaitlist","autoAcceptDecisions","shareFields","answers","approveBooking"],"properties":{"idempotencyKey":{"type":"string","format":"uuid","description":"Generate one per approval form. Resending the identical body returns the same booking with `200`; reusing the key with a different body returns `409`."},"childProfileId":{"type":"string","format":"uuid"},"childProfileVersion":{"type":"integer","minimum":1,"description":"The profile version the parent reviewed. If the profile has changed since, the request returns `409`."},"sessionId":{"type":"string","format":"uuid","description":"The catalog session to book."},"maxSpend":{"$ref":"#/components/schemas/BookingMoney","description":"The camp's spending limit, including the camp's own fees and tax, in the session’s currency and at most 2,500,000 cents. School’s Out’s service fee (`payment.serviceFeeCents`, from `GET /api/users/viewer/booking-policy`) is added on top: the hold on the parent’s card is this limit plus the fee, less `useCreditsCents`."},"allowWaitlist":{"type":"boolean","description":"Accept a waitlist place if the session is full."},"autoAcceptDecisions":{"type":"boolean","description":"Let School’s Out answer routine camp questions and accept session changes within the spending limit. It never raises spending above the limit, signs a waiver, or creates a camp account."},"shareFields":{"type":"array","items":{"$ref":"#/components/schemas/BookingShareableField"},"uniqueItems":true},"answers":{"type":"array","maxItems":50,"items":{"type":"object","additionalProperties":false,"required":["key","label","value"],"properties":{"key":{"type":"string","maxLength":100},"label":{"type":"string","maxLength":500},"value":{"type":["string","boolean","number"]}}}},"approveBooking":{"const":true,"description":"The parent’s explicit approval. Any other value returns `400`."},"useCreditsCents":{"type":"integer","minimum":1,"description":"Optional. Wallet credits to put toward the booking, at most the spending limit plus the service fee, and the wallet balance (`409` otherwise); USD bookings only. They are held at approval, reduce the card hold by the same amount, are applied before the card at capture, and are returned if the booking gets no place."},"paymentMethod":{"type":"string","enum":["default","backup"],"description":"Optional. Which of the parent’s saved cards to hold; defaults to the default card."},"reuseAllowed":{"const":true,"description":"Optional. The parent's consent, given with this approval, to reuse the child's saved details for registrations; it is saved on the child as `reuseAllowed` (the profile's owner only, `403` otherwise). Without it, a child whose profile does not allow reuse yet returns `409`. Once the profile allows reuse, approvals need not send it."},"profileFields":{"$ref":"#/components/schemas/FamilyChildFields","description":"Optional. Child details the parent chose to save to the child's profile with this booking (\"Save new details to profile\"). They are saved as `PATCH .../children/{childId}` saves `changes`, in the booking's transaction and from `childProfileVersion`, before the booking reads the profile; the booking records the new version. Details the parent does not save go in `answers`."}},"example":{"idempotencyKey":"0b7c3f52-9a41-4e2d-8f6a-3c1d5e7b9a20","childProfileId":"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93","childProfileVersion":4,"sessionId":"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48","maxSpend":{"amountCents":45000,"currency":"USD"},"allowWaitlist":true,"autoAcceptDecisions":false,"shareFields":["child.birthDate"],"answers":[{"key":"photo_consent","label":"Photo consent","value":false}],"approveBooking":true}},"BookingDecisionOption":{"type":"object","required":["optionId","label","total"],"properties":{"optionId":{"type":"string"},"label":{"type":"string"},"total":{"oneOf":[{"$ref":"#/components/schemas/BookingMoney"},{"type":"null"}],"description":"The camp’s total if the parent chooses this option."}}},"BookingPendingDecision":{"type":"object","required":["decisionId","code","message","options","proposedOptionId","expiresAt","screenshot"],"properties":{"decisionId":{"type":"string","format":"uuid"},"code":{"type":"string","enum":["unknown_question","choose_option","confirm_total","session_changed","waiver_signature","provider_account_required","unverified_step","answer_required","consent_required","registration_workflow_required"],"x-open-enum":true},"message":{"type":"string"},"options":{"type":"array","items":{"$ref":"#/components/schemas/BookingDecisionOption"}},"proposedOptionId":{"type":["string","null"]},"expiresAt":{"type":"string","format":"date-time","description":"When the camp’s checkout hold ends. After this, the booking fails with `decision_expired`."},"screenshot":{"description":"The camp’s page as Hallpass last saw it, for an `unverified_step` decision; null otherwise.","oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["mediaType","data","width","height","capturedAt"],"properties":{"mediaType":{"type":"string","enum":["image/jpeg","image/png"]},"data":{"type":"string","description":"Base64 of the image."},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1},"capturedAt":{"type":"string","format":"date-time"}}}]}}},"BookingDecisionRecord":{"type":"object","required":["decisionId","code","outcome","optionId","decidedBy","decidedAt"],"properties":{"decisionId":{"type":"string","format":"uuid"},"code":{"type":"string"},"outcome":{"type":"string","enum":["accepted","declined","expired"],"x-open-enum":true},"optionId":{"type":["string","null"]},"decidedBy":{"type":["string","null"],"enum":["parent","auto",null]},"decidedAt":{"type":"string","format":"date-time","description":"When the question was answered."}}},"BookingReceipt":{"type":"object","required":["providerReference","providerStatus","amount","confirmedAt","source"],"properties":{"providerReference":{"type":"string","description":"The camp’s confirmation or order number."},"providerStatus":{"type":"string","enum":["enrolled","waitlisted","application_received"],"x-open-enum":true,"description":"`application_received` on an applied booking."},"amount":{"oneOf":[{"$ref":"#/components/schemas/BookingMoney"},{"type":"null"}],"description":"What the parent was charged. Present only when there was a charge."},"confirmedAt":{"type":"string","format":"date-time","description":"When the camp confirmed the booking."},"source":{"type":"string","enum":["hallpass","parent"],"description":"`hallpass`: a provider receipt School’s Out verified. `parent`: the parent reported that an application became a registration; `amount` is then `null`."}}},"BookingFailure":{"type":"object","required":["code","message","retryable"],"properties":{"code":{"$ref":"#/components/schemas/BookingFailureCode"},"message":{"type":"string"},"retryable":{"type":"boolean","description":"Whether a new, re-approved booking might succeed."}}},"BookingFailureCode":{"type":"string","enum":["application_declined","blocked_by_provider","booking_expired","booking_rejected","deadline_exceeded","decision_declined","decision_expired","eligibility_mismatch","needs_person","not_available","payment_authentication_required","payment_declined","payment_hold_unresolved","payment_method_required","price_above_approval","provider_unavailable","registration_closed","sold_out","stopped_before_payment","unsupported_provider"],"description":"Why a booking didn't happen: Hallpass's camp booking codes (sold_out, price_above_approval, decision_expired, ...) and School's Out's own (payment_declined, payment_method_required, payment_hold_unresolved, booking_expired, booking_rejected, application_declined, ...). Apps map the codes they know and show the rest by `message`; a new code may be added without notice.","x-open-enum":true},"BookingPhase":{"type":"string","enum":["in_progress","needs_parent","applied","booked","waitlisted","not_booked","checking"],"description":"What the booking means to the parent, and which payload it carries. `in_progress`: School’s Out is booking it. `needs_parent`: the camp asked a question only the parent can answer; `decision` carries it. `applied`: the camp received an application and answers later; `receipt` carries it, nothing is charged, and the parent reports the answer with the outcome route. `booked`: the camp enrolled the child; `receipt` carries the confirmation (`receipt.source` is `parent` when the parent reported it). `waitlisted`: the child holds a waitlist place; `receipt` carries it. `not_booked`: the booking ended without a place; `failure` explains why, or is `null` when it was cancelled. `checking`: the outcome isn’t known yet; `checkingWith` says whether School’s Out is resolving the card hold or confirming with the camp. Never show `checking` as booked or failed. Adding a phase is a breaking change."},"FamilyBookingCommon":{"type":"object","description":"The fields every booking carries, whatever its phase.","required":["id","phase","status","paymentStatus","childProfileId","organization","activity","session","approval","progressStage","decisions","pollAfterMs","createdAt","updatedAt","waitlist","canceledBy","refund","dueOnProviderSite","note","askAgainAfter","replacesBookingId","replacedByBookingId","payment","cancellation","bookedBy","hold"],"properties":{"id":{"type":"string","format":"uuid"},"phase":{"$ref":"#/components/schemas/BookingPhase"},"status":{"$ref":"#/components/schemas/BookingStatus"},"paymentStatus":{"$ref":"#/components/schemas/BookingPaymentStatus"},"childProfileId":{"type":["string","null"],"format":"uuid","description":"`null` after the profile is deleted. The booking itself remains."},"organization":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"activity":{"anyOf":[{"type":"object","required":["id","name"],"additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}}},{"type":"null"}],"description":"The booked session's catalog activity, as it was at approval like organization and session; null when the session had none. A change of session stays within it."},"session":{"type":"object","required":["id","name","startDate","endDate","startTime","endTime","dates","dayTimes"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"startDate":{"type":["string","null"],"format":"date","description":"First day of the session."},"endDate":{"type":["string","null"],"format":"date","description":"Last day of the session."},"startTime":{"type":["string","null"],"pattern":"^[0-9]{2}:[0-9]{2}$","description":"Daily start time (HH:MM) when the session has one; snapshotted at approval."},"endTime":{"type":["string","null"],"pattern":"^[0-9]{2}:[0-9]{2}$","description":"Daily end time (HH:MM) when the session has one."},"dates":{"type":"array","maxItems":60,"items":{"type":"string","format":"date"},"description":"Published attendance dates, at most 60; empty when the camp published only a date range."},"dayTimes":{"type":"array","maxItems":60,"items":{"$ref":"#/components/schemas/FamilyPlanDayTime"},"description":"The days whose own times replace startTime and endTime, by date, snapshotted at approval: a day with different hours, or an overnight stay's drop-off (first day, startTime only) and pick-up (last day, endTime only) with neither between. Empty when every day keeps startTime and endTime."}}},"approval":{"type":"object","required":["approvedAt","maxSpend","allowWaitlist","autoAcceptDecisions","sharedFields"],"properties":{"approvedAt":{"type":"string","format":"date-time","description":"When the parent approved the booking."},"maxSpend":{"$ref":"#/components/schemas/BookingMoney"},"allowWaitlist":{"type":"boolean"},"autoAcceptDecisions":{"type":"boolean"},"sharedFields":{"type":"array","items":{"$ref":"#/components/schemas/BookingShareableField"},"description":"The optional fields the parent chose to share. Always-shared fields aren’t listed."}}},"progressStage":{"type":["string","null"],"enum":["resolving_session","filling_form","reviewing_total","paying","confirming",null],"x-open-enum":true,"description":"The furthest checkout stage Hallpass reported, or `null` before it reports one. New stages may be added."},"decisions":{"type":"array","items":{"$ref":"#/components/schemas/BookingDecisionRecord"}},"pollAfterMs":{"type":["integer","null"],"description":"How long to wait before reading the booking again. `null` once nothing will change without the parent."},"createdAt":{"type":"string","format":"date-time","description":"When the booking was created."},"updatedAt":{"type":"string","format":"date-time","description":"When the booking last changed."},"waitlist":{"oneOf":[{"type":"object","required":["position","updatedAt"],"properties":{"position":{"type":["integer","null"],"minimum":1,"description":"The place the camp reported, 1 is next; `null` when it gave none."},"updatedAt":{"type":"string","format":"date-time"}}},{"type":"null"}]},"canceledBy":{"type":["string","null"],"enum":["parent","provider","system",null],"description":"Who cancelled a cancelled booking."},"refund":{"oneOf":[{"type":"object","required":["method","amountCents","walletCents","status"],"properties":{"method":{"type":"string","enum":["credits","card"]},"amountCents":{"type":"integer","minimum":1,"description":"Everything the family paid, refunded."},"walletCents":{"type":"integer","minimum":0,"description":"The part returned to the wallet at once: all of it for `credits`, the credits part for `card`."},"status":{"type":"string","enum":["pending","done"],"description":"`pending` until the card refund is confirmed."}}},{"type":"null"}]},"dueOnProviderSite":{"oneOf":[{"type":"object","required":["label","url"],"properties":{"label":{"type":"string"},"url":{"type":"string","format":"uri"}},"description":"Something the parent must still do on the camp’s site; cleared by the outcome `provider_task_done`."},{"type":"null"}]},"note":{"type":["string","null"],"maxLength":1000,"description":"What the family should know for this booking, such as what to bring, from the provider’s email. An undone email update puts back the note it replaced."},"askAgainAfter":{"type":["string","null"],"format":"date-time","description":"Don’t ask about this application’s outcome again before this time."},"replacesBookingId":{"type":["string","null"],"format":"uuid","description":"The booking this one changes to another session."},"replacedByBookingId":{"type":["string","null"],"format":"uuid","description":"The live booking changing this one. This booking stays until that one is booked."},"payment":{"type":"object","required":["paymentMethod","creditsCents","cardHoldCents","serviceFeeCents"],"properties":{"paymentMethod":{"type":"string","enum":["default","backup"]},"creditsCents":{"type":"integer","minimum":0,"description":"Wallet credits held at approval; they pay the charge before the card."},"cardHoldCents":{"type":"integer","minimum":0,"description":"What is pending on the parent's card while it is held (paymentStatus hold_pending or held), else 0: the spending limit plus serviceFeeCents, less creditsCents, and at least Stripe's minimum charge ($0.50) when anything is left for the card. No hold is placed when the credits cover the whole of it. Capture charges only what the receipt and the service fee leave after the credits."},"serviceFeeCents":{"type":"integer","minimum":0,"description":"School’s Out’s service fee for this booking (one child), fixed at approval from the booking policy. It is charged, with the camp’s price, when the booking is booked or applied (an application costs only this fee); until then the card hold covers it. 0 for a booking that ended without it: waitlisted, or not booked. A refund after the camp cancels, or after a change, returns it."}},"additionalProperties":false},"cancellation":{"$ref":"#/components/schemas/FamilyBookingCancellation"},"bookedBy":{"anyOf":[{"type":"object","required":["userId","name"],"properties":{"userId":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}},"additionalProperties":false},{"type":"null"}],"description":"The family member (co-parent) who approved the booking; null when the family's owner did. The booking, its card hold and its payments are the owner's."},"hold":{"description":"Why a running run is waiting on something nobody can answer: Pilgrim holding an earlier task for a person's review, Pilgrim's assistance switched off at its Mac, or Hallpass retrying its worker. `null` while the run moves on its own.","oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["code","message","since"],"properties":{"code":{"type":"string","enum":["pilgrim_queue_held","pilgrim_assistance_off","executor_retrying","pilgrim_busy"],"x-open-enum":true},"message":{"type":"string","minLength":1,"maxLength":500,"description":"What the run waits for, written for the requester."},"since":{"type":"string","format":"date-time"}}}]}}},"FamilyBookingInProgress":{"description":"School’s Out is placing the card hold or booking with the camp.","allOf":[{"$ref":"#/components/schemas/FamilyBookingCommon"},{"type":"object","required":["phase","status","decision","receipt","failure","checkingWith"],"properties":{"phase":{"type":"string","const":"in_progress"},"status":{"type":"string","enum":["authorizing","prepared","submitting","submit_uncertain","accepted","running"],"x-open-enum":true},"decision":{"type":"null"},"receipt":{"type":"null"},"failure":{"type":"null"},"checkingWith":{"type":"null"}}}]},"FamilyBookingNeedsParent":{"description":"The camp asked a question only the parent can answer. Answer it before `decision.expiresAt`.","allOf":[{"$ref":"#/components/schemas/FamilyBookingCommon"},{"type":"object","required":["phase","status","decision","receipt","failure","checkingWith"],"properties":{"phase":{"type":"string","const":"needs_parent"},"status":{"type":"string","enum":["awaiting_decision"],"x-open-enum":true},"decision":{"$ref":"#/components/schemas/BookingPendingDecision"},"receipt":{"type":"null"},"failure":{"type":"null"},"checkingWith":{"type":"null"}}}]},"FamilyBookingBooked":{"description":"The camp enrolled the child. `receipt` has been checked against the approval. `paymentStatus` may still be `held` while the charge settles.","allOf":[{"$ref":"#/components/schemas/FamilyBookingCommon"},{"type":"object","required":["phase","status","decision","receipt","failure","checkingWith"],"properties":{"phase":{"type":"string","const":"booked"},"status":{"type":"string","enum":["confirmed"],"x-open-enum":true},"decision":{"type":"null"},"receipt":{"$ref":"#/components/schemas/BookingReceipt"},"failure":{"type":"null"},"checkingWith":{"type":"null"}}}]},"FamilyBookingWaitlisted":{"description":"The child holds a waitlist place. `receipt.amount` is present when the camp took a deposit.","allOf":[{"$ref":"#/components/schemas/FamilyBookingCommon"},{"type":"object","required":["phase","status","decision","receipt","failure","checkingWith"],"properties":{"phase":{"type":"string","const":"waitlisted"},"status":{"type":"string","enum":["waitlisted"],"x-open-enum":true},"decision":{"type":"null"},"receipt":{"$ref":"#/components/schemas/BookingReceipt"},"failure":{"type":"null"},"checkingWith":{"type":"null"}}}]},"FamilyBookingNotBooked":{"description":"The booking ended without a place. `failure` is `null` only when the parent cancelled cleanly.","allOf":[{"$ref":"#/components/schemas/FamilyBookingCommon"},{"type":"object","required":["phase","status","decision","receipt","failure","checkingWith"],"properties":{"phase":{"type":"string","const":"not_booked"},"status":{"type":"string","enum":["failed","cancelled"],"x-open-enum":true},"decision":{"type":"null"},"receipt":{"type":"null"},"failure":{"oneOf":[{"$ref":"#/components/schemas/BookingFailure"},{"type":"null"}]},"checkingWith":{"type":"null"}}}]},"FamilyBookingChecking":{"description":"The outcome isn’t known yet. Never show it as booked or failed.","allOf":[{"$ref":"#/components/schemas/FamilyBookingCommon"},{"type":"object","required":["phase","status","decision","receipt","failure","checkingWith"],"properties":{"phase":{"type":"string","const":"checking"},"status":{"type":"string","enum":["uncertain"],"x-open-enum":true},"decision":{"type":"null"},"receipt":{"type":"null"},"failure":{"type":"null"},"checkingWith":{"type":"string","enum":["card","camp"],"description":"`card`: a card hold School’s Out could not resolve. Nothing reached the camp, the parent may cancel, and the child may be booked again. `camp`: School’s Out is confirming the outcome with the camp."}}}]},"FamilyBooking":{"description":"A family booking. `phase` selects the variant and decides which of `decision`, `receipt`, `failure` and `checkingWith` are present.","oneOf":[{"$ref":"#/components/schemas/FamilyBookingInProgress"},{"$ref":"#/components/schemas/FamilyBookingNeedsParent"},{"$ref":"#/components/schemas/FamilyBookingApplied"},{"$ref":"#/components/schemas/FamilyBookingBooked"},{"$ref":"#/components/schemas/FamilyBookingWaitlisted"},{"$ref":"#/components/schemas/FamilyBookingNotBooked"},{"$ref":"#/components/schemas/FamilyBookingChecking"}],"discriminator":{"propertyName":"phase","mapping":{"in_progress":"#/components/schemas/FamilyBookingInProgress","needs_parent":"#/components/schemas/FamilyBookingNeedsParent","applied":"#/components/schemas/FamilyBookingApplied","booked":"#/components/schemas/FamilyBookingBooked","waitlisted":"#/components/schemas/FamilyBookingWaitlisted","not_booked":"#/components/schemas/FamilyBookingNotBooked","checking":"#/components/schemas/FamilyBookingChecking"}}},"BookingEventKind":{"type":"string","enum":["approval_granted","payment_hold_placed","payment_hold_failed","task_submitted","task_submit_uncertain","task_status_observed","decision_requested","decision_accepted","decision_declined","confirmation_received","payment_captured","payment_capture_failed","payment_released","cancel_requested","reconciled","change_requested","change_completed","outcome_reported","refund_chosen","refund_completed","credits_held","credits_released","credits_applied","group_rate_credited"],"x-open-enum":true,"description":"What happened. New kinds may be added; show `summary` for a kind you don’t recognise."},"BookingActor":{"type":"string","enum":["parent","auto","hallpass","system","staff"],"x-open-enum":true,"description":"Who caused the event. New actors may be added."},"BookingEvent":{"type":"object","required":["kind","status","actor","summary","createdAt"],"properties":{"kind":{"$ref":"#/components/schemas/BookingEventKind"},"status":{"$ref":"#/components/schemas/BookingStatus","description":"The booking’s status after this event."},"actor":{"$ref":"#/components/schemas/BookingActor"},"summary":{"type":"string","maxLength":500},"createdAt":{"type":"string","format":"date-time","description":"When the event was recorded."}}},"FamilyBookingDetail":{"allOf":[{"$ref":"#/components/schemas/FamilyBooking"},{"type":"object","required":["events"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/BookingEvent"},"description":"The full history, oldest first."}}}]},"BookingPaymentMethod":{"type":"object","required":["brand","last4"],"properties":{"brand":{"type":"string","description":"Can be empty; show “Card” in that case."},"last4":{"type":"string","pattern":"^[0-9]{4}$"}}},"CanonicalProvenanceRow":{"additionalProperties":false,"properties":{"configuration_id":{"format":"uuid","type":["string","null"]},"created_at":{"description":"Recorded bookkeeping timestamp; not a verification or freshness guarantee.","format":"date-time","type":["string","null"]},"external_ids":{"description":"Retained external identifiers when permitted by the field mask."},"field_provenance":{"$ref":"#/components/schemas/CanonicalFieldProvenance"},"id":{"format":"uuid","type":"string"},"merge_jobs":{"description":"Raw retained history, available only to ADMIN user-backed viewers.","items":{"additionalProperties":true,"type":"object"},"type":"array"},"relationship_jobs":{"description":"Raw retained history, available only to ADMIN user-backed viewers.","items":{"additionalProperties":true,"type":"object"},"type":"array"},"source_created_at":{"description":"Recorded bookkeeping timestamp; not a verification or freshness guarantee.","format":"date-time","type":["string","null"]},"source_records":{"description":"Raw retained history, available only to ADMIN user-backed viewers.","items":{"additionalProperties":true,"type":"object"},"type":"array"},"updated_at":{"description":"Recorded bookkeeping timestamp; not a verification or freshness guarantee.","format":"date-time","type":["string","null"]},"removed":{"type":"boolean","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_survivor_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"Present only with authorized showRemoved=true. Optional active same-kind survivor linked from the removed root; no data, descendants, or history move to it."},"removal_batch_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_review_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_review_version":{"type":["integer","null"],"readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_reason":{"type":["string","null"],"readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"field_resolution":{"$ref":"#/components/schemas/CanonicalFieldResolution","description":"Present when includeResolution=true; current effective state for the row fields allowed by the caller projection."},"field_sources":{"$ref":"#/components/schemas/CanonicalFieldSources","description":"Present when includeSources=true; current effective source identity for the row fields allowed by the caller projection."},"own_values":{"$ref":"#/components/schemas/CanonicalOwnValues","description":"Present when authorized includeOwnValues=true; entity-owned values, including nulls."}},"required":["id"],"type":"object"},"CrawlerAcquisitionBudget":{"additionalProperties":false,"properties":{"max_candidate_sites":{"type":["integer","null"]},"max_model_requests":{"type":["integer","null"]},"max_pages":{"type":["integer","null"]},"max_pages_per_site":{"type":["integer","null"]},"max_search_queries":{"type":["integer","null"]}},"required":["max_candidate_sites","max_model_requests","max_pages","max_pages_per_site","max_search_queries"],"type":"object"},"CrawlerAttempt":{"additionalProperties":false,"properties":{"attempt_number":{"type":"integer"},"completion_reason":{"type":["string","null"]},"evaluation_id":{"type":["string","null"]},"finished_at":{"format":"date-time","type":["string","null"]},"heartbeat_at":{"format":"date-time","type":"string"},"id":{"type":"string"},"instance_name":{"type":"string"},"job_id":{"type":"string"},"lease_expires_at":{"format":"date-time","type":"string"},"session_id":{"type":"string"},"started_at":{"format":"date-time","type":"string"},"status":{"type":"string","enum":["running","completed","partial","blocked","failed","expired","interrupted","cancelled"]}},"required":["attempt_number","completion_reason","evaluation_id","finished_at","id","job_id","lease_expires_at","session_id","started_at","status"],"type":"object"},"CrawlerComparison":{"additionalProperties":false,"properties":{"added":{"items":{"type":"string"},"type":"array"},"errors":{"items":{"type":"string"},"type":"array"},"output_urls":{"items":{"type":"string"},"type":"array"},"pending":{"items":{"type":"string"},"type":"array"},"removed":{"items":{"type":"string"},"type":"array"},"uncertain":{"items":{"type":"string"},"type":"array"},"unchanged":{"items":{"type":"string"},"type":"array"}},"required":["added","errors","output_urls","pending","removed","uncertain","unchanged"],"type":"object"},"CrawlerConfig":{"additionalProperties":false,"properties":{"id":{"type":"string"},"model":{"type":"string"},"prompt":{"type":"string"},"settings":{"additionalProperties":{},"type":"object"}},"required":["id","model","prompt","settings"],"type":"object"},"CrawlerCounts":{"additionalProperties":false,"properties":{"crawled":{"type":"integer"},"decided":{"type":"integer"},"discovered":{"type":"integer"},"errors":{"type":"integer"},"excluded":{"type":"integer"},"pending":{"type":"integer"},"retained":{"type":"integer"},"uncertain":{"type":"integer"}},"required":["crawled","decided","discovered","errors","excluded","pending","retained","uncertain"],"type":"object"},"CrawlerDetail":{"additionalProperties":false,"properties":{"attempt_count":{"type":"integer"},"attempts":{"items":{"$ref":"#/components/schemas/CrawlerAttempt"},"type":"array"},"attempts_truncated":{"type":"boolean"},"evaluation_count":{"type":"integer"},"evaluations":{"items":{"$ref":"#/components/schemas/CrawlerEvaluation"},"type":"array"},"evaluations_truncated":{"type":"boolean"},"event_count":{"type":"integer"},"events":{"items":{"$ref":"#/components/schemas/CrawlerEvent"},"type":"array"},"events_truncated":{"type":"boolean"},"job":{"$ref":"#/components/schemas/CrawlerJob"},"latest_progress":{"$ref":"#/components/schemas/CrawlerEvent"},"result_count":{"type":"integer"},"results":{"items":{"$ref":"#/components/schemas/CrawlerResult"},"type":"array"}},"required":["attempt_count","attempts","attempts_truncated","evaluation_count","evaluations","evaluations_truncated","event_count","events","events_truncated","job","result_count","results"],"type":"object"},"CrawlerDiscoveryPath":{"additionalProperties":false,"properties":{"providers":{"items":{"type":"string"},"type":"array"},"reason":{"type":"string"},"status":{"type":"string"},"steps":{"items":{"$ref":"#/components/schemas/CrawlerDiscoveryPathStep"},"type":"array"}},"required":["providers","reason","status","steps"],"type":"object"},"CrawlerDiscoveryPathStep":{"additionalProperties":false,"properties":{"url":{"type":"string"},"via":{"type":"string"}},"required":["url","via"],"type":"object"},"CrawlerEvaluationCounts":{"additionalProperties":false,"properties":{"added":{"type":"integer"},"crawled":{"type":"integer"},"decided":{"type":"integer"},"discovered":{"type":"integer"},"errors":{"type":"integer"},"excluded":{"type":"integer"},"pending":{"type":"integer"},"removed":{"type":"integer"},"retained":{"type":"integer"},"uncertain":{"type":"integer"}},"required":["added","crawled","decided","discovered","errors","excluded","pending","removed","retained","uncertain"],"type":"object"},"CrawlerEvaluationDetail":{"additionalProperties":false,"properties":{"comparison":{"$ref":"#/components/schemas/CrawlerComparison"},"evaluation":{"$ref":"#/components/schemas/CrawlerEvaluation"}},"required":["comparison","evaluation"],"type":"object"},"CrawlerEvaluationResult":{"additionalProperties":false,"properties":{"baseline_included":{"type":"boolean"},"depth":{"type":"integer"},"discovery_path":{"$ref":"#/components/schemas/CrawlerDiscoveryPath"},"excluded":{"type":"boolean"},"fetched_at":{"format":"date-time","type":["string","null"]},"final_url":{"type":"string"},"included":{"type":"boolean"},"no_probability":{"type":["number","null"]},"providers":{"items":{"type":"string"},"type":"array"},"reason":{"type":"string"},"site_assessment":{"$ref":"#/components/schemas/CrawlerSiteAssessment"},"site_preview":{"$ref":"#/components/schemas/CrawlerSiteAssessment"},"status":{"type":"string","enum":["discovered","crawled","blocked","failed","skipped"]},"url":{"type":"string"},"verdict":{"type":["string","null"],"enum":["relevant","irrelevant","uncertain","error",null]},"yes_probability":{"type":["number","null"]}},"required":["baseline_included","depth","excluded","fetched_at","final_url","included","no_probability","providers","reason","status","url","verdict","yes_probability"],"type":"object"},"CrawlerEvaluationResultCounts":{"additionalProperties":false,"properties":{"added":{"type":"integer"},"changed":{"type":"integer"},"errors":{"type":"integer"},"excluded":{"type":"integer"},"included":{"type":"integer"},"pending":{"type":"integer"},"removed":{"type":"integer"},"total":{"type":"integer"},"uncertain":{"type":"integer"}},"required":["added","changed","errors","excluded","included","pending","removed","total","uncertain"],"type":"object"},"CrawlerEvaluationResultsPage":{"type":"object","additionalProperties":false,"description":"One page of a JEV session's results, compared with its baseline.","required":["evaluation","baseline_evaluation_id","counts","results","total","page","pageSize"],"properties":{"evaluation":{"$ref":"#/components/schemas/CrawlerEvaluation"},"baseline_evaluation_id":{"type":["string","null"],"description":"The comparison baseline used; null means the original crawl outputs."},"counts":{"$ref":"#/components/schemas/CrawlerEvaluationResultCounts"},"results":{"type":"array","items":{"$ref":"#/components/schemas/CrawlerEvaluationResult"}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","minimum":1},"pageSize":{"type":"integer","minimum":1},"preview_counts":{"$ref":"#/components/schemas/CrawlerTridentPreviewCounts"},"preview_attempt_id":{"type":["string","null"]},"preview_revision":{"type":"string"}}},"CrawlerEvent":{"additionalProperties":false,"properties":{"attempt_id":{"type":["string","null"]},"created_at":{"format":"date-time","type":"string"},"details":{"additionalProperties":{},"type":"object"},"id":{"type":"string"},"job_id":{"type":"string"},"kind":{"type":"string"}},"required":["attempt_id","created_at","details","id","job_id","kind"],"type":"object"},"CrawlerJob":{"additionalProperties":false,"properties":{"acquisition_budget":{"$ref":"#/components/schemas/CrawlerAcquisitionBudget"},"activity_filter":{"type":"boolean"},"completion_reason":{"type":["string","null"]},"contract_version":{"type":"integer"},"counts":{"$ref":"#/components/schemas/CrawlerCounts"},"created_at":{"format":"date-time","type":"string"},"finished_at":{"format":"date-time","type":["string","null"]},"id":{"type":"string"},"initial_jev_config":{"$ref":"#/components/schemas/CrawlerConfig"},"initial_jev_config_id":{"type":["string","null"]},"latest_event":{"$ref":"#/components/schemas/CrawlerEvent"},"max_crawl_depth":{"type":["integer","null"]},"max_urls_per_site":{"type":["integer","null"]},"mode":{"type":"string","enum":["search","organization_website"]},"organization":{"$ref":"#/components/schemas/CrawlerOrganizationInput"},"provider_mask":{"type":"integer"},"queue":{"$ref":"#/components/schemas/CrawlerQueueEligibility"},"saved_results":{"$ref":"#/components/schemas/CrawlerSavedResults"},"search_exclude_domains":{"items":{"type":"string"},"type":"array"},"search_include_domains":{"items":{"type":"string"},"type":"array"},"search_term":{"type":"string"},"settings_mask":{"type":"integer"},"site_resolution":{"$ref":"#/components/schemas/CrawlerSiteResolution"},"stage":{"type":"string"},"status":{"type":"string","enum":["queued","running","completed","partial","blocked","failed","cancelled"]},"updated_at":{"format":"date-time","type":"string"}},"required":["activity_filter","completion_reason","contract_version","counts","created_at","finished_at","id","initial_jev_config_id","max_crawl_depth","max_urls_per_site","provider_mask","search_exclude_domains","search_include_domains","search_term","settings_mask","stage","status","updated_at"],"type":"object"},"CrawlerOrganizationInput":{"additionalProperties":false,"properties":{"address":{"type":"string"},"description":{"type":"string"},"email":{"type":"string"},"location":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string"},"registration_url":{"type":"string"},"source_url":{"type":"string"},"website_url":{"type":"string"}},"required":["name"],"type":"object"},"CrawlerQueueEligibility":{"additionalProperties":false,"properties":{"active_workers":{"type":"integer"},"available_at":{"format":"date-time","type":"string"},"compatible_workers":{"type":"integer"},"message":{"type":"string"},"ready_workers":{"type":"integer"},"state":{"type":"string"},"workers":{"items":{"$ref":"#/components/schemas/CrawlerQueueWorker"},"type":"array"},"workers_truncated":{"type":"boolean"}},"required":["active_workers","available_at","compatible_workers","message","ready_workers","state","workers","workers_truncated"],"type":"object"},"CrawlerQueueWorker":{"additionalProperties":false,"properties":{"busy":{"type":"boolean"},"missing_provider_mask":{"type":"integer"},"missing_settings_mask":{"type":"integer"},"name":{"type":"string"},"supports_contract":{"type":"boolean"}},"required":["busy","missing_provider_mask","missing_settings_mask","name","supports_contract"],"type":"object"},"CrawlerResult":{"additionalProperties":false,"properties":{"depth":{"type":"integer"},"discovery_path":{"$ref":"#/components/schemas/CrawlerDiscoveryPath"},"excluded":{"type":"boolean"},"fetched_at":{"format":"date-time","type":["string","null"]},"final_url":{"type":"string"},"no_probability":{"type":["number","null"]},"providers":{"items":{"type":"string"},"type":"array"},"reason":{"type":"string"},"site_assessment":{"$ref":"#/components/schemas/CrawlerSiteAssessment"},"status":{"type":"string","enum":["discovered","crawled","blocked","failed","skipped"]},"url":{"type":"string"},"verdict":{"type":["string","null"],"enum":["relevant","irrelevant","uncertain","error",null]},"yes_probability":{"type":["number","null"]}},"required":["depth","excluded","fetched_at","final_url","no_probability","providers","reason","status","url","verdict","yes_probability"],"type":"object"},"CrawlerResultsPage":{"type":"object","additionalProperties":false,"description":"One page of a crawl's saved results, from its latest or chosen attempt.","required":["results","total","attempt","counts","site_resolution","page","pageSize"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/CrawlerResult"}},"total":{"type":"integer","minimum":0},"attempt":{"oneOf":[{"$ref":"#/components/schemas/CrawlerAttempt"},{"type":"null"}]},"counts":{"$ref":"#/components/schemas/CrawlerCounts"},"site_resolution":{"oneOf":[{"$ref":"#/components/schemas/CrawlerSiteResolution"},{"type":"null"}]},"page":{"type":"integer","minimum":1},"pageSize":{"type":"integer","minimum":1}}},"CrawlerSavedResults":{"additionalProperties":false,"properties":{"attempt_id":{"type":"string"},"attempt_number":{"type":"integer"},"counts":{"$ref":"#/components/schemas/CrawlerCounts"}},"required":["attempt_id","attempt_number","counts"],"type":"object"},"CrawlerSiteAssessment":{"additionalProperties":false,"properties":{"activity_reason":{"type":"string"},"activity_status":{"type":"string","enum":["relevant","irrelevant","unresolved","blocked"]},"decision":{"type":"string","enum":["eligible","rejected","unresolved"]},"discovery":{"items":{"$ref":"#/components/schemas/CrawlerSiteDiscovery"},"type":"array"},"evidence":{"items":{"$ref":"#/components/schemas/CrawlerSiteEvidence"},"type":"array"},"homepage_url":{"type":["string","null"]},"identity_reason":{"type":"string"},"identity_status":{"type":"string","enum":["verified","not_applicable","ambiguous","unresolved","rejected"]},"policy_version":{"type":"integer"},"reason":{"type":"string"},"root_url":{"type":"string"},"site_role":{"type":"string","enum":["first_party","aggregator","directory","social","shared_platform","unknown"]}},"required":["activity_reason","activity_status","decision","discovery","evidence","homepage_url","identity_reason","identity_status","policy_version","reason","root_url","site_role"],"type":"object"},"CrawlerSiteDiscovery":{"additionalProperties":false,"properties":{"provider":{"type":"string"},"query":{"type":"string"},"source_summary":{"type":"string"},"source_url":{"type":"string"},"url":{"type":"string"}},"required":["url"],"type":"object"},"CrawlerSiteEvidence":{"additionalProperties":false,"properties":{"content_sha256":{"type":"string"},"excerpt":{"type":"string"},"fetched_at":{"format":"date-time","type":"string"},"kind":{"type":"string"},"url":{"type":"string"}},"required":["content_sha256","excerpt","fetched_at","kind","url"],"type":"object"},"CrawlerSiteResolution":{"additionalProperties":false,"properties":{"homepage_url":{"type":["string","null"]},"reason":{"type":"string"},"status":{"type":"string"},"usage":{"$ref":"#/components/schemas/CrawlerSiteUsage"}},"required":["homepage_url","reason","status","usage"],"type":"object"},"CrawlerSiteUsage":{"additionalProperties":false,"properties":{"candidate_sites":{"type":"integer"},"cost_usd":{"type":["number","null"]},"model_requests":{"type":"integer"},"page_fetches":{"type":"integer"},"search_queries":{"type":"integer"},"transport_request_limit":{"type":["integer","null"]},"transport_requests":{"type":"integer"}},"required":["candidate_sites","cost_usd","model_requests","page_fetches","search_queries","transport_request_limit","transport_requests"],"type":"object"},"CrawlerTridentPreviewCounts":{"additionalProperties":false,"properties":{"checked":{"type":"integer"},"eligible":{"type":"integer"},"errors":{"type":"integer"},"ineligible":{"type":"integer"},"pending":{"type":"integer"},"total":{"type":"integer"},"uncertain":{"type":"integer"}},"required":["checked","eligible","errors","ineligible","pending","total","uncertain"],"type":"object"},"ScraperArtifact":{"additionalProperties":false,"properties":{"attempt_id":{"type":"string"},"body":{},"created_at":{"format":"date-time","type":"string"},"id":{"type":"integer"},"key":{"type":"string"},"kind":{"type":"string"},"omitted":{"type":"string"},"size_bytes":{"type":"integer"}},"required":["attempt_id","created_at","id","kind","size_bytes"],"type":"object"},"ScraperArtifactSummary":{"additionalProperties":false,"properties":{"attempt_id":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"id":{"type":"integer"},"key":{"type":"string"},"kind":{"type":"string"},"omitted":{"type":"string"},"size_bytes":{"type":"integer"}},"required":["attempt_id","created_at","id","kind","size_bytes"],"type":"object"},"ScraperAttempt":{"additionalProperties":false,"properties":{"error":{"type":"string"},"finished_at":{"format":"date-time","type":"string"},"id":{"type":"string"},"job_id":{"type":"string"},"lease_expires_at":{"format":"date-time","type":"string"},"number":{"type":"integer"},"session_id":{"type":"string"},"started_at":{"format":"date-time","type":"string"},"status":{"type":"string","enum":["running","completed","failed","expired","interrupted","paused"]}},"required":["id","job_id","lease_expires_at","number","session_id","started_at","status"],"type":"object"},"ScraperCodexAccount":{"additionalProperties":false,"properties":{"code_expires_at":{"format":"date-time","type":["string","null"]},"created_at":{"format":"date-time","type":"string"},"created_by":{"type":"string"},"device_url":{"type":["string","null"]},"email":{"type":["string","null"]},"id":{"type":"string"},"label":{"type":"string"},"last_error":{"type":["string","null"]},"last_used_at":{"format":"date-time","type":["string","null"]},"leased_at":{"format":"date-time","type":["string","null"]},"leased_by":{"oneOf":[{"$ref":"#/components/schemas/ScraperCodexAccountLessee"},{"type":"null"}]},"plan":{"type":["string","null"]},"resets_5h_at":{"format":"date-time","type":["string","null"],"description":"When the five-hour window resets, as last read."},"resets_at":{"format":"date-time","type":["string","null"]},"resets_week_at":{"format":"date-time","type":["string","null"],"description":"When the weekly window resets, as last read."},"status":{"type":"string","enum":["pending_login","awaiting_approval","active","exhausted","login_failed","login_required","removing","removed"]},"updated_at":{"format":"date-time","type":"string"},"usage_ceiling_pct":{"type":["integer","null"],"minimum":1,"maximum":100,"description":"Operator's usage ceiling in percent, or null for none. While used_5h_pct or used_week_pct is at or above it the subscription stays active and logged in but workers do not lease it."},"used_5h_pct":{"type":["number","null"]},"used_week_pct":{"type":["number","null"]},"user_code":{"type":["string","null"]}},"required":["code_expires_at","created_at","created_by","device_url","email","id","label","last_error","last_used_at","leased_at","leased_by","plan","resets_5h_at","resets_at","resets_week_at","status","updated_at","usage_ceiling_pct","used_5h_pct","used_week_pct","user_code"],"type":"object"},"ScraperCodexAccountLessee":{"additionalProperties":false,"properties":{"instance_id":{"type":"string"},"name":{"type":"string"}},"required":["instance_id","name"],"type":"object"},"ScraperCodexAccountRef":{"additionalProperties":false,"properties":{"email":{"type":["string","null"]},"id":{"type":"string"},"label":{"type":"string"}},"required":["email","id","label"],"type":"object"},"ScraperExcludedSiteReason":{"type":"object","additionalProperties":false,"required":["code","label","description"],"properties":{"code":{"type":"string","description":"Registered reason code, such as not_camp_provider or other. The list comes from GET /api/ingestion/excluded-sites/reasons, not a fixed enum."},"label":{"type":"string"},"description":{"type":"string"}}},"ScraperExcludedSiteOrganization":{"type":"object","additionalProperties":false,"required":["id","name","activity_count","session_count","blacklisted","blacklisted_at","removed","removed_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"activity_count":{"type":"integer","minimum":0,"description":"Undeleted activities visible under canonical removal."},"session_count":{"type":"integer","minimum":0,"description":"Undeleted sessions visible under canonical removal: a session under a removed activity or organisation does not count."},"blacklisted":{"type":"boolean","description":"Hidden by organization_moderation.blacklisted_at; POST or DELETE /api/organizations/{id}/blacklist changes it."},"blacklisted_at":{"format":"date-time","type":["string","null"]},"removed":{"type":"boolean","description":"Canonically removed (removed_at set)."},"removed_at":{"format":"date-time","type":["string","null"]}}},"ScraperExcludedSiteLiveData":{"type":"object","additionalProperties":false,"required":["organization_count","organizations"],"description":"Read when the site is listed, never stored: undeleted organisations whose website (configuration canonical_url) has the excluded host, www-insensitive, removed ones included.","properties":{"organization_count":{"type":"integer","minimum":0,"description":"Every matching organisation."},"organizations":{"type":"array","maxItems":20,"description":"At most 20, live ones first, then by name.","items":{"$ref":"#/components/schemas/ScraperExcludedSiteOrganization"}}}},"ScraperExcludedSite":{"type":"object","additionalProperties":false,"required":["id","url","host","reason_code","reason_label","comment","source","source_id","revisit_at","created_by_user_id","created_by_email","created_at","removed_at","removed_by_user_id","removed_by_email","removal_comment"],"properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","description":"The address as entered."},"host":{"type":"string","description":"Lowercased hostname without scheme, port, path or a leading www.; what every enforcement point matches."},"reason_code":{"type":"string"},"reason_label":{"type":"string","description":"The registered label, or the code when it is no longer registered."},"comment":{"type":"string"},"source":{"type":"string","enum":["inbox","scraper_job","exclusions_page"]},"source_id":{"type":["string","null"]},"revisit_at":{"format":"date-time","type":["string","null"]},"created_by_user_id":{"type":["string","null"]},"created_by_email":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"removed_at":{"format":"date-time","type":["string","null"],"description":"Set when un-excluded; the exclusion is active while null."},"removed_by_user_id":{"type":["string","null"]},"removed_by_email":{"type":["string","null"]},"removal_comment":{"type":["string","null"]},"live_data":{"$ref":"#/components/schemas/ScraperExcludedSiteLiveData","description":"Present on list, create and remove responses; absent from lookups."}}},"ScraperExcludedSiteLookup":{"type":"object","additionalProperties":false,"required":["input","host","excluded","site"],"properties":{"input":{"type":"string","description":"The url or host as sent."},"host":{"type":"string","description":"The normalised host, or empty when the input is not a website address or host."},"excluded":{"type":"boolean"},"site":{"oneOf":[{"$ref":"#/components/schemas/ScraperExcludedSite"},{"type":"null"}],"description":"The active exclusion, without live_data, or null."}}},"ScraperEvent":{"additionalProperties":false,"properties":{"activities":{"type":"integer"},"attempt_id":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"detail":{"type":"string"},"duration_milliseconds":{"type":"integer"},"exit_code":{"type":"integer"},"id":{"type":"integer"},"message":{"type":"string"},"metrics":{"additionalProperties":{"type":"integer"},"type":"object"},"model":{"type":"string"},"operation":{"type":"string"},"organizations":{"type":"integer"},"pages":{"type":"integer"},"sessions":{"type":"integer"},"stage":{"type":"string"},"state":{"type":"string","enum":["running","succeeded","failed","canceled"]},"target":{"type":"string"},"warnings":{"items":{"type":"string"},"type":"array"}},"required":["attempt_id","created_at","id","stage","state"],"type":"object"},"ScraperBuildStatus":{"additionalProperties":false,"type":"object","properties":{"revision":{"type":"string"},"main_revision":{"oneOf":[{"type":"string","pattern":"^[0-9a-f]{40}$"},{"type":"null"}]},"behind_main":{"oneOf":[{"type":"integer","minimum":0},{"type":"null"}]},"checked_at":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]}},"required":["revision","main_revision","behind_main","checked_at"]},"ScraperUpdateRequest":{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string","format":"uuid"},"action":{"type":"string","enum":["update","rollback"]},"status":{"type":"string","enum":["pending","draining","running","succeeded","rolled_back","failed","refused"]},"target_revision":{"oneOf":[{"type":"string","pattern":"^[0-9a-f]{40}$"},{"type":"null"}]},"from_revision":{"oneOf":[{"type":"string","pattern":"^[0-9a-f]{40}$"},{"type":"null"}]},"to_revision":{"oneOf":[{"type":"string","pattern":"^[0-9a-f]{40}$"},{"type":"null"}]},"message":{"oneOf":[{"type":"string","maxLength":300},{"type":"null"}]},"log_tail":{"oneOf":[{"type":"string","maxLength":4096},{"type":"null"}]},"requested_by":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"finished_at":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]}},"required":["id","action","status","target_revision","from_revision","to_revision","message","log_tail","requested_by","created_at","updated_at","finished_at"]},"ScraperInstance":{"additionalProperties":false,"properties":{"availability":{"type":"string"},"codex_account":{"oneOf":[{"$ref":"#/components/schemas/ScraperCodexAccountRef"},{"type":"null"}]},"created_at":{"format":"date-time","type":"string"},"current_job":{"$ref":"#/components/schemas/ScraperJob"},"id":{"type":"string"},"last_seen_at":{"format":"date-time","type":"string"},"name":{"type":"string"},"provider_pauses":{"items":{"$ref":"#/components/schemas/ScraperProviderPause"},"type":"array"},"session":{"$ref":"#/components/schemas/ScraperSession"},"build":{"$ref":"#/components/schemas/ScraperBuildStatus"},"current_jobs":{"type":"array","items":{"$ref":"#/components/schemas/ScraperJob"}},"update":{"oneOf":[{"$ref":"#/components/schemas/ScraperUpdateRequest"},{"type":"null"}]}},"required":["availability","codex_account","created_at","id","name","build","current_jobs","update"],"type":"object"},"IngestionInbox":{"additionalProperties":false,"type":"object","required":["publications","total","counts","auto_approval","limit","offset"],"properties":{"publications":{"type":"array","items":{"$ref":"#/components/schemas/IngestionInboxPublication"}},"total":{"type":"integer","minimum":0},"counts":{"$ref":"#/components/schemas/IngestionInboxCounts"},"auto_approval":{"additionalProperties":false,"type":"object","required":["mode","allow_new_sites"],"properties":{"mode":{"type":"string","enum":["off","shadow","on"]},"allow_new_sites":{"type":"boolean"}}},"limit":{"type":"integer","minimum":1},"offset":{"type":"integer","minimum":0}}},"IngestionInboxCounts":{"additionalProperties":false,"type":"object","description":"Every site's counts whatever the kind filter; the verdict counts are 0 in mode off.","required":["all","refresh","new","auto_approve","has_flags","passes_except_new_site","passes_except_sessions"],"properties":{"all":{"type":"integer","minimum":0},"refresh":{"type":"integer","minimum":0},"new":{"type":"integer","minimum":0},"auto_approve":{"type":"integer","minimum":0},"has_flags":{"type":"integer","minimum":0},"passes_except_new_site":{"type":"integer","minimum":0},"passes_except_sessions":{"type":"integer","minimum":0}}},"IngestionInboxPublication":{"additionalProperties":false,"type":"object","required":["normalized_url","organization_name","kind","records_hash","captured_at","waiting_since","superseded_count","activity_count","session_count","unchanged_from_live","activity_delta","session_delta","batches","job_id","decided_by_person","approval","overseer_request"],"properties":{"normalized_url":{"type":"string"},"organization_name":{"type":"string","description":"The staged organization record's name, else the site URL."},"kind":{"type":"string","enum":["refresh","new"]},"records_hash":{"type":"string"},"captured_at":{"type":["string","null"],"format":"date-time"},"waiting_since":{"type":"string","format":"date-time","description":"When the site's oldest publication still awaiting approval was staged, superseded ones included."},"superseded_count":{"type":"integer","minimum":0},"activity_count":{"type":"integer","minimum":0},"session_count":{"type":"integer","minimum":0},"unchanged_from_live":{"type":"boolean"},"activity_delta":{"type":["integer","null"]},"session_delta":{"type":["integer","null"]},"batches":{"type":"array","items":{"$ref":"#/components/schemas/IngestionInboxBatch"}},"job_id":{"type":["string","null"]},"decided_by_person":{"type":"boolean","description":"A person has decided records of this publication in the Inbox. The website approval policy then leaves the rest to them: its verdict is needs_review, led by a failing decided_by_person check."},"approval":{"anyOf":[{"$ref":"#/components/schemas/IngestionInboxApproval"},{"type":"null"}]},"overseer_request":{"anyOf":[{"$ref":"#/components/schemas/IngestionInboxOverseerRequest"},{"type":"null"}],"description":"The one-off Overseer request a decision on this publication asked for, if any."}}},"IngestionInboxBatch":{"additionalProperties":false,"type":"object","required":["id","target_kind","status","record_count","eligible_count","merged_count","failed_count","pending_count","denied_count","reviewer_edits"],"properties":{"id":{"type":"string"},"target_kind":{"type":"string","enum":["organization","activity","session"]},"status":{"type":"string","description":"The batch's status. A batch whose approved records are being mapped or merged keeps its pending records in the Inbox; one whose approved records settled returns to awaiting_approval until none is pending; one whose every record was denied ends rejected."},"record_count":{"type":"integer","minimum":0},"eligible_count":{"type":"integer","minimum":0},"merged_count":{"type":"integer","minimum":0},"failed_count":{"type":"integer","minimum":0},"pending_count":{"type":"integer","minimum":0,"description":"Records still awaiting a decision. The batch's other records not denied were approved."},"denied_count":{"type":"integer","minimum":0,"description":"Records denied in the Inbox; their merge jobs ended rejected and never merge."},"reviewer_edits":{"$ref":"#/components/schemas/IngestionInboxReviewerEditCounts"}}},"IngestionInboxApproval":{"additionalProperties":false,"type":"object","required":["verdict","policy_version","passes_except_new_site","passes_except_sessions","has_flags","checks"],"properties":{"verdict":{"type":"string","enum":["auto_approve","needs_review"]},"policy_version":{"type":"integer","minimum":1},"passes_except_new_site":{"type":"boolean"},"passes_except_sessions":{"type":"boolean","description":"Every check but has_sessions (and site_approved_before) passes: approvable by hand in bulk."},"has_flags":{"type":"boolean"},"checks":{"type":"array","items":{"additionalProperties":false,"type":"object","required":["key","passed","label","reason"],"properties":{"key":{"type":"string"},"passed":{"type":"boolean"},"label":{"type":"string"},"reason":{"type":"string"}}},"description":"The policy's checks in order; the first failing one's reason is the verdict's tag. decided_by_person leads, failing, only for a publication a person has decided records of (policy 4)."}}},"IngestionInboxDecisionRequest":{"additionalProperties":false,"type":"object","required":["batch_ids","confirm"],"properties":{"batch_ids":{"type":"array","minItems":1,"maxItems":3,"uniqueItems":true,"items":{"type":"string","format":"uuid"},"description":"Every batch of one website publication, as the Inbox row lists them."},"approve":{"type":"array","items":{"additionalProperties":false,"type":"object","required":["record_id"],"properties":{"record_id":{"type":"string","format":"uuid"},"edits":{"type":"object","additionalProperties":true,"description":"Field values the reviewer corrects, keyed by field: the organisation update form's fields for an organisation, the program or session form's for an activity or session, each checked as that form checks it with the reviewer as submitter, so a bad edit returns 400 before anything is approved. A value can't be null, blank or an empty list, and no text may hold a NUL. The staged record is approved and merged unchanged; the edits then apply as the reviewer's own change once it merges and any identity review of it closes. An empty object is no edit."}}},"description":"Records to approve, each optionally with the reviewer's edits; an unknown field returns 400."},"deny":{"type":"array","items":{"additionalProperties":false,"type":"object","required":["record_id","reason"],"properties":{"record_id":{"type":"string","format":"uuid"},"reason":{"type":"string","minLength":1,"maxLength":1000}}},"description":"Records to deny, each with the reviewer's reason. Denying an activity also denies its pending sessions in the publication."},"overseer":{"additionalProperties":false,"type":"object","properties":{"note":{"type":"string","maxLength":2000,"description":"The reviewer's note, added to the request. No NUL characters."}},"description":"Asks Overseer, once the publication's organisation has merged, to fill the gaps the publication's failed approval checks found, with the note. Needs the organisation approved now or earlier, or already live; a publication asks once."},"confirm":{"const":true}},"description":"Names at least one and at most 5000 records, each once. Records not named stay pending."},"IngestionInboxDecision":{"additionalProperties":false,"type":"object","required":["batches","approved_record_ids","denied_record_ids","denied_with_activity_record_ids","denied_with_organization_record_ids","edited_record_ids","overseer_request"],"properties":{"batches":{"type":"array","items":{"$ref":"#/components/schemas/IngestionInboxBatch"},"description":"The publication's batches after the decision, in approval order."},"approved_record_ids":{"type":"array","items":{"type":"string"}},"denied_record_ids":{"type":"array","items":{"type":"string"},"description":"Every record denied, including sessions denied with their activity and records denied with a new site's organisation."},"denied_with_activity_record_ids":{"type":"array","items":{"type":"string"},"description":"Pending sessions denied because the decision denied their activity."},"denied_with_organization_record_ids":{"type":"array","items":{"type":"string"},"description":"Pending records denied because the decision denied a new site's organisation (neither approved earlier nor live)."},"edited_record_ids":{"type":"array","items":{"type":"string"},"description":"Approved records the decision gave reviewer edits."},"overseer_request":{"anyOf":[{"$ref":"#/components/schemas/IngestionInboxOverseerRequest"},{"type":"null"}],"description":"The publication's Overseer request, if this or an earlier decision asked for one."}}},"IngestionInboxReviewerEditCounts":{"additionalProperties":false,"type":"object","required":["pending","applied","failed"],"properties":{"pending":{"type":"integer","minimum":0,"description":"Edits waiting for their record to merge (and an activity or session to be linked to its organisation)."},"applied":{"type":"integer","minimum":0},"failed":{"type":"integer","minimum":0}},"description":"The batch's records approved with reviewer edits, by the edits' state."},"IngestionInboxOverseerRequest":{"additionalProperties":false,"type":"object","required":["request_id","batch_ids","reviewer_user_id","note","text","decided_at","state","error","organization_id","entity_configuration_id","queued_at"],"properties":{"request_id":{"type":"string","format":"uuid","description":"The Overseer requestId, fixed for the publication so every retry sends the same request."},"batch_ids":{"type":"array","items":{"type":"string"},"description":"The publication's batches, which find its organisation record."},"reviewer_user_id":{"type":"string","description":"The reviewer, who authors the Overseer request."},"note":{"type":["string","null"]},"text":{"type":"string","description":"The request: the site, the publication's failed approval checks and the reviewer's note."},"decided_at":{"type":"string","format":"date-time"},"state":{"type":"string","enum":["pending","queued","failed"],"description":"pending until the organisation has merged (and any identity review of it closed) and Overseer holds the request; queued once it does; failed when it never can."},"error":{"type":["string","null"],"description":"Why the request failed, or why a pending one is still waiting."},"organization_id":{"type":["string","null"]},"entity_configuration_id":{"type":["string","null"],"description":"The organisation's configuration the request is bound to."},"queued_at":{"type":["string","null"],"format":"date-time"}},"description":"A one-off Overseer request an Inbox decision asked for."},"ScraperFailureCode":{"additionalProperties":false,"type":"object","required":["code","count"],"properties":{"code":{"type":"string"},"count":{"type":"integer","minimum":1}}},"ScraperJobQualitySummary":{"additionalProperties":false,"description":"Coverage from a job's latest stored quality report. Percents are null when the report counts no sessions or omits the field.","properties":{"dated_percent":{"type":["number","null"]},"flags":{"minimum":0,"type":"integer"},"priced_percent":{"type":["number","null"]},"sessions":{"minimum":0,"type":"integer"}},"required":["sessions","dated_percent","priced_percent","flags"],"type":"object"},"ScraperJobSummary":{"additionalProperties":false,"properties":{"batches":{"items":{"$ref":"#/components/schemas/ScraperJobSummaryBatch"},"type":"array"},"quality":{"anyOf":[{"$ref":"#/components/schemas/ScraperJobQualitySummary"},{"type":"null"}]}},"required":["batches","quality"],"type":"object"},"ScraperJobSummaryBatch":{"additionalProperties":false,"properties":{"record_count":{"minimum":0,"type":"integer"},"status":{"type":"string"},"target_kind":{"enum":["organization","activity","session"],"type":"string"}},"required":["target_kind","status","record_count"],"type":"object"},"ScraperJob":{"additionalProperties":false,"properties":{"batch_id":{"type":"string"},"contract_version":{"type":"integer"},"created_at":{"format":"date-time","type":"string"},"error":{"type":"string"},"id":{"type":"string"},"instance_id":{"type":"string"},"kind":{"type":"string","enum":["onboarding","refresh","adapter_repair","booking_inspection"]},"latest_event":{"$ref":"#/components/schemas/ScraperEvent"},"max_attempts":{"type":"integer"},"not_before":{"format":"date-time","type":"string"},"parent_job_id":{"type":"string"},"priority":{"description":"Claim order set by hand: an installation claims its highest-priority eligible queued job first, oldest first within a priority. Absent means 0 (normal); negative is low.","type":"integer"},"settings":{"$ref":"#/components/schemas/ScraperScrapeSettings"},"status":{"type":"string","enum":["queued","running","completed","failed","paused"]},"summary":{"$ref":"#/components/schemas/ScraperJobSummary"},"unsupported_reason":{"type":"string"},"updated_at":{"format":"date-time","type":"string"},"website":{"type":"string"}},"required":["contract_version","created_at","id","instance_id","kind","max_attempts","not_before","status","updated_at","website"],"type":"object"},"ScraperJobDetail":{"additionalProperties":false,"properties":{"attempts":{"items":{"$ref":"#/components/schemas/ScraperAttempt"},"type":"array"},"batch_ids":{"items":{"type":"string"},"type":"array"},"event_count":{"type":"integer"},"events":{"items":{"$ref":"#/components/schemas/ScraperEvent"},"type":"array"},"events_truncated":{"type":"boolean"},"job":{"$ref":"#/components/schemas/ScraperJob"},"publication":{"$ref":"#/components/schemas/ScraperWebsitePublication"}},"required":["attempts","batch_ids","event_count","events","events_truncated","job"],"type":"object"},"ScraperProviderPause":{"additionalProperties":false,"properties":{"paused_at":{"format":"date-time","type":"string"},"probe_error":{"type":"string"},"probed_at":{"format":"date-time","type":"string"},"probes":{"type":"integer"},"provider":{"type":"string","enum":["typesafe","codex","openai","anthropic","google","geonode"]},"reason":{"type":"string"}},"required":["paused_at","probes","provider","reason"],"type":"object"},"ScraperScrapeSettings":{"additionalProperties":false,"properties":{"approved_external_origin":{"type":"string"},"capture_timeout_ms":{"type":"integer"},"crawl_delay_ms":{"type":"integer"},"dom_quiet_window_ms":{"type":"integer"},"include_subdomains":{"type":"boolean"},"max_corpus_pages":{"type":"integer"},"max_discovery_pages":{"type":"integer"},"max_model_calls":{"type":"integer"},"max_typesafe_requests":{"type":"integer"},"booking_session_id":{"type":"string","format":"uuid","description":"Canonical session target; required only for booking_inspection jobs."}},"type":"object"},"ScraperSession":{"additionalProperties":false,"properties":{"build_revision":{"type":"string"},"heartbeat_at":{"format":"date-time","type":"string"},"id":{"type":"string"},"instance_id":{"type":"string"},"started_at":{"format":"date-time","type":"string"},"stopped_at":{"format":"date-time","type":"string"},"supported_versions":{"items":{"type":"integer"},"type":"array"}},"required":["build_revision","heartbeat_at","id","instance_id","started_at","supported_versions"],"type":"object"},"ScraperWebsitePublication":{"additionalProperties":false,"properties":{"batches":{"type":["array","null"],"items":{"$ref":"#/components/schemas/ScraperWebsitePublicationBatchStatus"},"description":"Null before any batch has been staged."},"has_errors":{"type":"boolean"},"needs_review":{"type":"boolean"},"relationships":{"type":["array","null"],"items":{"$ref":"#/components/schemas/ScraperWebsitePublicationRelationshipStatus"},"description":"Null when the website has no staged relationships."},"settled":{"type":"boolean"}},"required":["batches","has_errors","needs_review","relationships","settled"],"type":"object"},"ScraperWebsitePublicationBatchStatus":{"additionalProperties":false,"properties":{"eligible_count":{"type":"integer"},"failed_count":{"type":"integer"},"id":{"type":"string"},"merged_count":{"type":"integer"},"record_count":{"type":"integer"},"result_ids":{"type":["array","null"],"items":{"type":"string"},"description":"Canonical IDs produced by applied merge results, excluding ignored removed matches and records held for review. Null when no applied results exist. Batch settlement or a retained matched identity does not establish that canonical data was applied."},"status":{"type":"string"},"target_kind":{"type":"string"},"ignored_removed_count":{"type":"integer","minimum":0,"description":"Merge receipts skipped because the canonical record or its parent was removed. They did not apply new canonical data and their retained matched identities do not appear in result_ids."},"held_for_review_count":{"type":"integer","minimum":0,"description":"Merge receipts held for explicit review because the available evidence could not safely resolve a removed canonical identity. They do not establish canonical application and do not appear in result_ids."},"superseded_by_batch_ids":{"type":"array","items":{"type":"string"},"description":"Batch IDs of the newer website publication of the same site that superseded this batch, while it awaits approval or still holds records pending an Inbox decision; approving or deciding them then returns 409. Empty when the batch is current or nothing of it is pending."},"pending_count":{"type":"integer","minimum":0,"description":"Records still awaiting an Inbox decision."},"denied_count":{"type":"integer","minimum":0,"description":"Records denied in the Inbox; their merge jobs ended rejected and never merge."}},"required":["eligible_count","failed_count","id","merged_count","record_count","result_ids","status","target_kind","ignored_removed_count","held_for_review_count","superseded_by_batch_ids","pending_count","denied_count"],"type":"object"},"ScraperWebsitePublicationRelationshipStatus":{"additionalProperties":false,"properties":{"id":{"type":"string"},"relationship_kind":{"type":"string"},"review_required":{"type":"boolean"},"status":{"type":"string"}},"required":["id","relationship_kind","review_required","status"],"type":"object"},"OverseerDirective":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","minLength":1,"maxLength":100,"description":"Stable caller-assigned identity, unique in this list; at most 100 UTF-8 bytes."},"text":{"type":"string","minLength":1,"description":"Nonempty instruction. Saved after trimming surrounding whitespace; all item text together is at most 65536 UTF-8 bytes."},"enabled":{"type":"boolean","description":"Only enabled instructions are supplied as current standing instructions to the worker."}},"required":["id","text","enabled"]},"OverseerToolOption":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"}},"required":["name","label","description"]},"OverseerMessage":{"type":"object","additionalProperties":false,"properties":{"requestId":{"type":"string","format":"uuid"},"revision":{"type":"integer","minimum":1,"description":"Current execution revision owning this work. Settings replacement may move unfinished work to a newer revision."},"expectedRevision":{"type":"integer","minimum":0,"description":"Immutable expected revision supplied when the request was submitted. Match uncertain submissions against this value, even when the current execution revision changes."},"text":{"type":"string"},"actorId":{"type":"string"},"status":{"type":"string","enum":["queued","delivered","cancelled"],"description":"Delivered means supplied to a successfully completed agent turn, not proof that requested work finished."},"createdAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":["string","null"],"format":"date-time"},"cancelledAt":{"type":["string","null"],"format":"date-time"},"workStatus":{"type":"string","enum":["queued","in_progress","blocked","completed","cancelled","unknown"],"description":"Explicit request progress. Unknown preserves delivered requests predating progress tracking without claiming completion."},"workVersion":{"type":"integer","minimum":1},"progress":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"entityConfigurationId":{"type":["string","null"],"format":"uuid","description":"Supplied configuration ID for a request; corrections resolve the original version entity configuration. No target kind is persisted."},"field":{"type":["string","null"],"description":"Catalog field key the request is about, such as refund_policy, or media for the gallery, cover and logo. Null for whole-record requests and for corrections."},"targetKind":{"type":["string","null"],"enum":["organization","activity","session",null],"description":"Server-resolved canonical record kind. Null when no unambiguous record is available."},"targetId":{"type":["string","null"],"format":"uuid","description":"Server-resolved exact canonical record ID for existing canonical tools."},"targetName":{"type":["string","null"],"description":"Display label for the exact canonical target."}},"required":["requestId","revision","expectedRevision","text","actorId","status","createdAt","deliveredAt","cancelledAt","workStatus","workVersion","progress","updatedAt","completedAt","entityConfigurationId","field","targetKind","targetId","targetName"]},"FamilyAccountViewerProfile":{"description":"The signed-in parent's identity. phone is the editable contact phone; phoneVerified is true while it is the account's verified phone (POST /api/auth/phone/check). Verifying a phone does not turn on two-step login: SMS two-step login is its own switch (POST and DELETE /api/users/viewer/two-step/sms) that texts the verified phone.","properties":{"fullName":{"maxLength":120,"type":["string","null"],"description":"The parent's name: their legacy profile first and last name joined by a space. Writing it splits it at its first space."},"preferredName":{"maxLength":60,"type":["string","null"]},"email":{"type":"string"},"emailVerified":{"type":"boolean"},"pendingEmail":{"description":"The address an email change waits on (POST /api/users/viewer/email-change) until its link is confirmed; null when none. email stays the sign-in address until then.","format":"email","type":["string","null"]},"phone":{"description":"E.164.","type":["string","null"]},"phoneVerified":{"description":"phone is the account's verified phone, which texts (notifications, SMS two-step login) go to.","type":"boolean"},"hasPassword":{"description":"The account can sign in with a password. False for an account that signs in only with Google, a magic link or a passkey.","type":"boolean"},"passwordChangedAt":{"description":"When the password was set (at sign-up or enrollment) or last changed. Null when hasPassword is false, and also for a password stored before this date was recorded.","format":"date-time","type":["string","null"]},"twoStepEnabled":{"description":"At least one second factor is enrolled (twoStepMethods is not empty).","type":"boolean"},"twoStepMethods":{"description":"The enrolled second factors, in the order sign-in offers them: passkey (a passkey is registered) and sms (SMS two-step login is on).","type":"array","uniqueItems":true,"items":{"enum":["passkey","sms"],"type":"string"}}},"required":["fullName","preferredName","email","emailVerified","pendingEmail","phone","phoneVerified","hasPassword","passwordChangedAt","twoStepEnabled","twoStepMethods"],"type":"object","additionalProperties":false},"FamilyAccountViewer":{"description":"The viewer response. Only profile is part of the family contract; the snake_case user, credits, access and organizations fields are retained for Intelligence and are not described here.","properties":{"profile":{"$ref":"#/components/schemas/FamilyAccountViewerProfile"}},"required":["profile"],"type":"object"},"FamilyAccountDeviceSession":{"description":"A signed-in device. It names no place: the API has no trustworthy coarse location for a sign-in.","properties":{"id":{"format":"uuid","type":"string"},"device":{"description":"Browser and platform from the User-Agent at sign-in, for example Chrome on macOS.","type":"string"},"kind":{"enum":["laptop","phone","other"],"type":"string"},"lastActiveAt":{"description":"Updated at most once per 15 minutes.","format":"date-time","type":"string"},"createdAt":{"format":"date-time","type":"string"},"current":{"description":"The session this request's cookie names.","type":"boolean"}},"required":["id","device","kind","lastActiveAt","createdAt","current"],"type":"object","additionalProperties":false},"FamilyAccountExport":{"description":"A data export. expiresAt is when the emailed download link lapses; until then it opens any number of times.","properties":{"id":{"format":"uuid","type":"string"},"status":{"enum":["pending","running","ready","failed","expired"],"type":"string"},"requestedAt":{"format":"date-time","type":"string"},"readyAt":{"format":"date-time","type":["string","null"]},"expiresAt":{"format":"date-time","type":["string","null"]}},"required":["id","status","requestedAt","readyAt","expiresAt"],"type":"object","additionalProperties":false},"FamilyAccountPlace":{"description":"A place the parent can choose as the household's home area, its home, or one of its saved places.","properties":{"placeId":{"description":"Opaque; accepted by PATCH /api/users/viewer/household for one hour.","type":"string"},"label":{"type":"string","description":"The area the place is in (\"Sellwood, Portland, OR\"), also for a street address."},"address":{"type":["string","null"],"maxLength":200,"description":"The street address a search matched; null for an area (a city or ZIP code) or a device's location."},"postalCode":{"type":["string","null"]},"region":{"description":"Two-letter US state code.","type":["string","null"]},"point":{"properties":{"latitude":{"maximum":90,"minimum":-90,"type":"number"},"longitude":{"maximum":180,"minimum":-180,"type":"number"}},"required":["latitude","longitude"],"type":"object","additionalProperties":false},"precision":{"$ref":"#/components/schemas/FamilyAccountPlacePrecision"},"served":{"description":"Inside an area School's Out serves.","type":"boolean"}},"required":["placeId","label","address","postalCode","region","point","precision","served"],"type":"object","additionalProperties":false},"FamilyAccountPlacePrecision":{"enum":["building","parcel","street","locality","region","unknown","device"],"type":"string","description":"How exactly a place's point places it. building, parcel and street are a geocoded street address; locality and region an area's centre; device the point a device sent (Use my location); unknown a geocoder answer without one. building, parcel, street and device are street level: only a match at one of them carries an address."},"FamilyAccountLocation":{"description":"Where the household's home is, as a place lookup resolved it: set from a placeId (see PATCH /api/users/viewer/household); clients never send its point or address.","type":"object","additionalProperties":false,"required":["label","address","point","precision"],"properties":{"label":{"type":"string","minLength":1,"maxLength":120,"description":"The parent's name for it, else the area the lookup named."},"address":{"type":["string","null"],"maxLength":200,"description":"The street address the lookup matched; null for an area or a device's location."},"point":{"properties":{"latitude":{"maximum":90,"minimum":-90,"type":"number"},"longitude":{"maximum":180,"minimum":-180,"type":"number"}},"required":["latitude","longitude"],"type":"object","additionalProperties":false},"precision":{"$ref":"#/components/schemas/FamilyAccountPlacePrecision"}}},"FamilyAccountSavedPlace":{"description":"One of the household's saved places besides its home, located like the home from a placeId.","type":"object","additionalProperties":false,"required":["id","kind","childId","label","address","point","precision"],"properties":{"id":{"type":"string","format":"uuid","description":"Send it back to keep the place (and its location); leave it out with a placeId for a new place."},"kind":{"enum":["work","school","other"],"type":"string"},"childId":{"type":["string","null"],"format":"uuid","description":"For a school place, the child whose school it is: a school the school directory does not list. At most one school place per child; null otherwise."},"label":{"type":"string","minLength":1,"maxLength":120},"address":{"type":["string","null"],"maxLength":200},"point":{"properties":{"latitude":{"maximum":90,"minimum":-90,"type":"number"},"longitude":{"maximum":180,"minimum":-180,"type":"number"}},"required":["latitude","longitude"],"type":"object","additionalProperties":false},"precision":{"$ref":"#/components/schemas/FamilyAccountPlacePrecision"}}},"FamilyAccountSearchArea":{"description":"The household's home area. point is set only from a placeId (see PATCH); clients cannot send one.","properties":{"label":{"maxLength":120,"minLength":1,"type":"string"},"postalCode":{"pattern":"^[0-9]{5}$","type":"string"},"region":{"pattern":"^[A-Z]{2}$","type":"string"},"point":{"properties":{"latitude":{"maximum":90,"minimum":-90,"type":"number"},"longitude":{"maximum":180,"minimum":-180,"type":"number"}},"required":["latitude","longitude"],"type":"object","additionalProperties":false},"radiusMiles":{"maximum":50,"minimum":1,"type":"integer"},"fromHome":{"type":"boolean","description":"Present and true when the search area is centred on the household's home (the same point). Derived on every save; a sent value is ignored."}},"required":["label"],"type":"object","additionalProperties":false},"FamilyAccountEmergencyContact":{"properties":{"id":{"type":"string","format":"uuid","description":"Sent on every contact the API returns. Send it back to keep the contact (children point at it); leave it out for a new contact."},"name":{"maxLength":255,"type":"string"},"relationship":{"maxLength":60,"type":"string","description":"May be empty, as for contacts added from a child profile."},"phone":{"maxLength":64,"type":"string","description":"E.164 when it parses as a phone number, otherwise as typed."}},"required":["name","relationship","phone"],"type":"object","additionalProperties":false,"description":"One of the household's emergency contacts. Children point at it by id, so their emergency contact follows its edits; a name or a phone may be empty, never both."},"FamilyAccountPlanningIntent":{"properties":{"planning":{"items":{"enum":["after_school","summer","school_break","weekend","browsing"],"type":"string"},"type":"array"},"neededWhen":{"enum":["this_month","next_few_months","planning_ahead",null],"type":["string","null"]}},"required":["planning","neededWhen"],"type":"object","additionalProperties":false},"FamilyAccountHouseholdReferral":{"description":"The household's stable referral code, how many accounts joined with it, and how many it admits in all.","properties":{"code":{"pattern":"^[A-Z]{1,8}-[0-9]{2,6}$","type":"string"},"joined":{"minimum":0,"type":"integer"},"limit":{"description":"How many families the code admits in all, at sign-up or by redemption; joined counts toward it. Once joined reaches it, the code is refused with referral_limit_reached.","minimum":0,"type":"integer"}},"required":["code","joined","limit"],"type":"object","additionalProperties":false},"FamilyAccountReferralRedemption":{"properties":{"code":{"type":"string"},"redeemedAt":{"format":"date-time","type":"string"},"credited":{"description":"Whether the wallet granted the referral credit. While referral rewards are off, a redemption is recorded without credit and stays false; while they are on, a credit not granted yet is granted later.","type":"boolean"}},"required":["code","redeemedAt","credited"],"type":"object","additionalProperties":false},"FamilyAccountUpcomingPlan":{"properties":{"id":{"type":"string"},"title":{"type":"string"},"startsOn":{"format":"date","type":"string"},"status":{"type":"string"}},"required":["id","title","startsOn","status"],"type":"object","additionalProperties":false},"FamilyAccountCalendarSync":{"type":"object","required":["confirmed","waitlisted"],"properties":{"confirmed":{"type":"boolean","description":"Write booked sessions to School's Out's calendar in this account."},"waitlisted":{"type":"boolean","description":"Write waitlisted sessions to School's Out's calendar in this account."}}},"FamilyAccountCalendarFailure":{"type":"object","required":["code","at"],"properties":{"code":{"type":"string","x-open-enum":true,"enum":["authorization_failed","authorization_revoked","refresh_failed","push_failed"],"description":"authorization_failed: the provider refused a connection attempt (an existing connection keeps working). authorization_revoked: the provider no longer accepts the stored grant or password; reconnect. refresh_failed: reading events failed. push_failed: making School's Out's own calendar or writing bookings to it failed."},"at":{"type":"string","format":"date-time"}}},"FamilyAccountGoogleCalendarConnection":{"type":"object","required":["connected","emailAddress","connectedAt","lastSyncedAt","sync","failure","calendars","needsRegrant"],"properties":{"connected":{"type":"boolean"},"emailAddress":{"type":["string","null"],"description":"The connected Google account, which may differ from the sign-in email; null when disconnected."},"connectedAt":{"type":["string","null"],"format":"date-time"},"lastSyncedAt":{"type":["string","null"],"format":"date-time","description":"When the event cache last refreshed successfully."},"sync":{"$ref":"#/components/schemas/FamilyAccountCalendarSync"},"failure":{"oneOf":[{"$ref":"#/components/schemas/FamilyAccountCalendarFailure"},{"type":"null"}],"description":"The last authorization or sync failure; null once a sync succeeds."},"calendars":{"type":"array","items":{"$ref":"#/components/schemas/FamilyAccountCalendarChoice"},"description":"The parent's calendars as last listed (School's Out's own excluded); empty before the first refresh or when disconnected. A grant without the calendar-list scope lists only the primary calendar."},"needsRegrant":{"type":"boolean","description":"True when the grant predates or declined calendar.calendarlist.readonly: only the primary calendar is read until the parent connects again."}},"description":"The single-connection shape of the /google-calendar routes, which act on the viewer's first Google connection (in connection order). Kept for app releases before multiple accounts; read GET /api/users/viewer/calendars for every connection."},"FamilyAccountCalendarConnection":{"type":"object","additionalProperties":false,"required":["id","provider","account","connectedAt","lastSyncedAt","sync","failure","calendars","needsRegrant"],"description":"One of the viewer's own calendar accounts, without its secret.","properties":{"id":{"type":"string","format":"uuid"},"provider":{"type":"string","x-open-enum":true,"enum":["google","apple"],"description":"google: a Google account. apple: an Apple iCloud account."},"account":{"type":"string","description":"The connected account, lowercased: the Google account address (which may differ from the sign-in email) or the Apple ID."},"connectedAt":{"type":"string","format":"date-time"},"lastSyncedAt":{"type":["string","null"],"format":"date-time","description":"When the event cache last refreshed successfully."},"sync":{"$ref":"#/components/schemas/FamilyAccountCalendarSync"},"failure":{"oneOf":[{"$ref":"#/components/schemas/FamilyAccountCalendarFailure"},{"type":"null"}],"description":"The last authorization or sync failure; null once a sync succeeds."},"calendars":{"type":"array","items":{"$ref":"#/components/schemas/FamilyAccountCalendarChoice"},"description":"The account's calendars and which are read; empty until the first refresh lists them."},"needsRegrant":{"type":"boolean","description":"True when a Google grant cannot list calendars, so only its primary calendar is read until the parent reconnects. Always false for Apple."}}},"FamilyAccountCalendarConnections":{"type":"object","additionalProperties":false,"required":["connections"],"properties":{"connections":{"type":"array","items":{"$ref":"#/components/schemas/FamilyAccountCalendarConnection"},"description":"In the order they were first connected."}}},"FamilyAccountCalendarEvent":{"type":"object","required":["id","calendar","title","date","endDate","allDay","startTime","endTime","htmlLink","childIds","busy","adult"],"properties":{"id":{"type":"string","description":"The viewer's own event: its provider event ID, stable for links and googleEventCovers. A busy block: an opaque stable ID (busy:<hash>)."},"calendar":{"oneOf":[{"$ref":"#/components/schemas/FamilyAccountCalendarRef"},{"type":"null"}],"description":"The viewer's calendar the event came from; null for a busy block."},"title":{"type":["string","null"],"description":"Null for a busy block."},"date":{"type":"string","format":"date","description":"Local start date in the household time zone."},"endDate":{"type":"string","format":"date","description":"Local end date, inclusive."},"allDay":{"type":"boolean"},"startTime":{"type":["string","null"],"pattern":"^[0-2][0-9]:[0-5][0-9]$","description":"Local HH:MM; null when all day."},"endTime":{"type":["string","null"],"pattern":"^[0-2][0-9]:[0-5][0-9]$","description":"Local HH:MM; null when all day."},"htmlLink":{"type":["string","null"],"description":"The event's page in the provider's web calendar; null for a busy block and for providers without one (Apple)."},"childIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"The viewer's accessible children whose first name (or planning label) appears in the title of the viewer's own event as a whole word, case-insensitively. Unordered. Always empty for a busy block."},"busy":{"type":"boolean","description":"True for another adult's event, shown as a busy block."},"adult":{"$ref":"#/components/schemas/FamilyAccountCalendarAdult"}},"description":"One event of the family's merged Google Calendar read: every connected adult's (owner, co-parent and caregiver) cached events. The viewer's own events are whole (busy: false). Another adult's event is a busy block (busy: true): only its time span and adult; calendar, title and htmlLink are null, childIds is empty and id is opaque. The API leaves the rest out of the response; another adult's calendar list is never shown. Another adult's copy of an event the viewer has too shows once, as the viewer's."},"FamilyAccountCalendarEvents":{"type":"object","required":["events","stale"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/FamilyAccountCalendarEvent"}},"stale":{"type":"boolean","description":"True when a connected account's last refresh failed and its older cached events are served."}}},"FamilyAccountCalendarDisconnection":{"type":"object","required":["connected","revoked"],"properties":{"connected":{"const":false,"type":"boolean"},"revoked":{"type":"boolean","description":"Whether the provider confirmed the grant revoked or already invalid."}}},"FamilyAccountGoogleCalendarAuthorization":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"Google consent URL for web; one-use API browser bootstrap URL for native."}}},"FamilyAccountCalendarRef":{"type":"object","additionalProperties":false,"required":["id","name"],"description":"The calendar an event came from.","properties":{"id":{"type":"string","description":"The provider's calendar ID: a Google calendar ID (a primary calendar's is the Google account address) or an Apple calendar's CalDAV URL."},"name":{"type":"string","description":"The calendar's name as the parent sees it, such as Family or Work."}}},"FamilyAccountCalendarChoice":{"type":"object","additionalProperties":false,"required":["id","name","primary","selected"],"description":"One of the parent's calendars and whether its events are read.","properties":{"id":{"type":"string"},"name":{"type":"string"},"primary":{"type":"boolean"},"selected":{"type":"boolean","description":"Read into the event cache. Without a saved choice, the calendars Google Calendar shows are read."}}},"FamilyPlanSchool":{"type":"object","additionalProperties":false,"required":["id","name","kind","city","region","postalCode","distanceMiles","calendarStatus"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"kind":{"enum":["public","private","charter","preschool"]},"city":{"type":"string"},"region":{"type":"string","pattern":"^[A-Z]{2}$"},"postalCode":{"oneOf":[{"type":"string","pattern":"^[0-9]{5}$"},{"type":"null"}]},"distanceMiles":{"oneOf":[{"type":"number","minimum":0,"description":"From the household's home, to a tenth of a mile; null when no home point is known or the school has no location."},{"type":"null"}]},"calendarStatus":{"enum":["published","pending","none"],"description":"published: the school's calendar source published days in its current term; pending: a calendar source is linked but has published nothing for the current term; none: no calendar source."}}},"FamilyPlanChildSchool":{"type":"object","additionalProperties":false,"required":["schoolId","name","kind","address","point"],"properties":{"schoolId":{"oneOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"name":{"type":"string","description":"The school's name, the name the parent typed, or a default label for an answer without one."},"kind":{"enum":["public","private","charter","preschool","homeschool","other"],"description":"The school's kind; for an answer that names no school, private, homeschool, or other (not found)."},"address":{"type":["string","null"],"description":"A school's address on one line, its street first when the directory has one (\"5700 SE 39th Ave, Portland, OR 97202\"); null for an answer that names no school."},"point":{"type":["object","null"],"additionalProperties":false,"required":["latitude","longitude"],"properties":{"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180}},"description":"The school's location from its directory, as every family location reads (the household's home and places: point {latitude, longitude}); null when it has none, or for an answer that names no school."}}},"FamilyPlanSchoolCalendarDay":{"type":"object","additionalProperties":false,"required":["date","kind","label","breakId","breakName","endsAt","source","updateId"],"properties":{"date":{"type":"string","format":"date"},"kind":{"enum":["no_school","break","early_release","holiday"]},"label":{"type":"string"},"breakId":{"oneOf":[{"type":"string","description":"Groups the consecutive days of one break."},{"type":"null"}]},"breakName":{"oneOf":[{"type":"string"},{"type":"null"}]},"endsAt":{"oneOf":[{"type":"string","pattern":"^[0-9]{2}:[0-9]{2}$","description":"When students are released on an early-release day."},{"type":"null"}]},"source":{"enum":["school","email","parent"]},"updateId":{"oneOf":[{"type":"string","format":"uuid","description":"The email update that set the day, which can be undone."},{"type":"null"}]}}},"FamilyPlanSchoolCalendarSchool":{"type":"object","additionalProperties":false,"required":["schoolId","name","childIds","status","term","weekdays","hours","days"],"properties":{"schoolId":{"oneOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"name":{"type":"string"},"childIds":{"type":"array","items":{"type":"string","format":"uuid"}},"status":{"enum":["published","pending","none"]},"term":{"oneOf":[{"type":"object","additionalProperties":false,"required":["start","end"],"properties":{"start":{"type":"string","format":"date"},"end":{"type":"string","format":"date"}}},{"type":"null"}]},"weekdays":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6},"description":"Days the school is normally in session, 0 = Monday."},"hours":{"oneOf":[{"type":"object","additionalProperties":false,"required":["start","end"],"properties":{"start":{"type":"string","pattern":"^[0-9]{2}:[0-9]{2}$"},"end":{"type":"string","pattern":"^[0-9]{2}:[0-9]{2}$"}}},{"type":"null"}]},"days":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanSchoolCalendarDay"},"description":"Days out or released early, after the family's overrides, by date. Days in session are not listed. Summer is a break labeled Summer break on the school's weekdays: after the term's last day through Labor Day, and before a term that starts after June 15 and by September, from June 15; a published day or an override on those dates wins."}}},"FamilyPlanSchoolCalendar":{"type":"object","additionalProperties":false,"required":["schools"],"properties":{"schools":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanSchoolCalendarSchool"}}}},"FamilyPlanError":{"type":"object","required":["error","message"],"additionalProperties":false,"properties":{"error":{"const":true},"message":{"type":"string","description":"A sentence you can show the parent as is."},"data":{"type":"null"}}},"FamilyPlanRecurrence":{"type":"object","required":["weekdays","from","until"],"additionalProperties":false,"properties":{"weekdays":{"type":"array","minItems":1,"maxItems":7,"uniqueItems":true,"items":{"type":"integer","minimum":0,"maximum":6},"description":"Days of the week, 0 = Monday."},"from":{"type":"string","format":"date"},"until":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Last day, inclusive; null while it goes on."}}},"FamilyPlan":{"type":"object","required":["id","source","childIds","title","organizationId","activityId","sessionId","dates","startTime","endTime","dayTimes","recurrence","status","askAgainAfter","group","coveredBy","note","bookingId","editable","organizationName","price","canceledBy","dueOnProviderSite"],"additionalProperties":false,"properties":{"id":{"type":"string","pattern":"^(booking|plan):[0-9a-f-]{36}$","description":"\"booking:<uuid>\" for a Hallpass booking, \"plan:<uuid>\" for a plan made here."},"source":{"enum":["booking","external","covered"]},"childIds":{"type":"array","items":{"type":"string","format":"uuid"}},"title":{"type":"string"},"organizationId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"organizationName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The camp's name: as the booking was made, else the catalog's live camp; null without one."},"activityId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"sessionId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"dates":{"type":"array","maxItems":400,"items":{"type":"string","format":"date"},"description":"The occurrences inside the requested range (all of them, up to 400, when no range was requested), earliest first. A recurrence is expanded; a booking that published only a date range lists every day of it."},"startTime":{"anyOf":[{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},{"type":"null"}]},"endTime":{"anyOf":[{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},{"type":"null"}]},"dayTimes":{"type":"array","maxItems":400,"items":{"$ref":"#/components/schemas/FamilyPlanDayTime"},"description":"The plan's own times on those of its listed dates whose times differ from startTime and endTime: a multi-day booking whose session keeps different hours on different days, or an overnight stay (drop-off on its first day, pick-up on its last, neither between). Earliest first; empty when every listed date keeps startTime and endTime."},"recurrence":{"anyOf":[{"$ref":"#/components/schemas/FamilyPlanRecurrence"},{"type":"null"}]},"status":{"enum":["booked","applied","needs_parent","waitlisted","registered","unconfirmed","covered","canceled","failed"],"description":"Bookings map their phase: booked, applied, needs_parent and waitlisted as is; in_progress and checking are unconfirmed; not_booked is canceled when someone cancelled it, else failed. External plans hold registered, applied (the family applied on the provider’s site and waits for its answer), needs_parent (it still has to finish there) or unconfirmed (nobody said yet), the same words as the booking phases."},"askAgainAfter":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Set only for applied: Home doesn't ask whether the application became a registration before this time (null asks now). A booking plan carries its booking's askAgainAfter."},"group":{"enum":["active","pending","waitlist","closed","past"],"description":"active: booked, registered or covered. pending: applied, needs_parent or unconfirmed. waitlist: waitlisted. closed: canceled or failed. past: any plan that is not closed and whose last day is before today."},"coveredBy":{"anyOf":[{"enum":["family","sitter","other"]},{"type":"null"}]},"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"bookingId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"price":{"anyOf":[{"$ref":"#/components/schemas/BookingMoney"},{"type":"null"}],"description":"A booking's listed price; null for a plan made here or a session that stated none."},"canceledBy":{"anyOf":[{"enum":["parent","provider","system"]},{"type":"null"}],"description":"Who canceled a canceled booking; null otherwise."},"dueOnProviderSite":{"anyOf":[{"$ref":"#/components/schemas/FamilyPlanProviderTask"},{"type":"null"}],"description":"Something the parent must still do on the provider's site for a booking that is not closed; null otherwise."},"editable":{"type":"boolean","description":"True for a plan the viewer made. Plans for a shared child are visible to the child's owner and all of its guardians, but only the adult who made one may change or delete it; booking plans change through their booking."}}},"FamilyPlanInput":{"type":"object","additionalProperties":false,"required":["childIds","source"],"description":"Send exactly one of dates and recurrence, except for an external plan with a sessionId, which may send neither and takes the session's days. Without a title, sessionId or activityId names the plan from the catalog.","properties":{"childIds":{"type":"array","minItems":1,"uniqueItems":true,"items":{"type":"string","format":"uuid"},"description":"Children the viewer owns or is a guardian of."},"source":{"enum":["external","covered"],"description":"external: registered on the provider's own site. covered: the family covers the days itself."},"sessionId":{"type":"string","format":"uuid"},"activityId":{"type":"string","format":"uuid"},"title":{"type":"string","minLength":1,"maxLength":120,"description":"Required without sessionId or activityId."},"dates":{"type":"array","minItems":1,"maxItems":400,"uniqueItems":true,"items":{"type":"string","format":"date"},"description":"The plan's days. Optional for an external plan with a sessionId: left out (with recurrence), the session's own days are used."},"recurrence":{"$ref":"#/components/schemas/FamilyPlanRecurrence"},"startTime":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","description":"Send with endTime. Left out on an external plan that takes its session's days, the session's daily clock is used when it has one."},"endTime":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},"coveredBy":{"enum":["family","sitter","other"],"description":"Required for covered plans, and only for them."},"status":{"enum":["registered","applied","needs_parent","unconfirmed"],"default":"registered","description":"External plans only. applied sets askAgainAfter, so Home asks about the application later."}}},"FamilyPlanChangeInput":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"status":{"enum":["registered","applied","canceled"],"description":"registered and applied apply to external plans only. applied again is \"not yet\" for an applied plan: it resets askAgainAfter."},"dates":{"type":"array","minItems":1,"maxItems":400,"uniqueItems":true,"items":{"type":"string","format":"date"},"description":"Replaces the occurrences, including a recurrence."},"note":{"anyOf":[{"type":"string","minLength":1,"maxLength":500},{"type":"null"}],"description":"null removes the note."}}},"FamilyPlanGoogleEventCover":{"type":"object","required":["eventId","covers"],"additionalProperties":false,"properties":{"eventId":{"type":"string","minLength":1,"maxLength":200},"covers":{"type":"boolean"}}},"FamilyPlanChildDayMarks":{"type":"object","additionalProperties":false,"properties":{"need":{"anyOf":[{"const":"not_needed"},{"type":"null"}],"description":"\"We're covered\" without saying how."},"googleEventCovers":{"anyOf":[{"$ref":"#/components/schemas/FamilyPlanGoogleEventCover"},{"type":"null"}],"description":"Whether a Google event covers the day."},"conflict":{"anyOf":[{"enum":["skip","leave","fine"]},{"type":"null"}]},"hiddenIdeas":{"anyOf":[{"type":"array","maxItems":50,"items":{"type":"string","format":"uuid"}},{"type":"null"}],"description":"Activity ids the family hid for the day; an empty list or null clears them."}}},"FamilyPlanDayMarks":{"type":"object","additionalProperties":false,"description":"Absent keys are unset. In a request, a present key replaces the stored value and null clears it; a child set to null clears all of that child's marks.","properties":{"open":{"anyOf":[{"enum":["open","not"]},{"type":"null"}],"description":"Household-level; friends who share plans see 'open'."},"children":{"type":"object","description":"Keyed by child id.","additionalProperties":{"anyOf":[{"$ref":"#/components/schemas/FamilyPlanChildDayMarks"},{"type":"null"}]}}}},"FamilyPlanCalendarCover":{"type":"object","required":["state","source","planId","title","activityId"],"additionalProperties":false,"properties":{"state":{"enum":["planned","idea","open","not_needed"],"description":"First match: planned (an active plan, or a Google event the family said covers the day), not_needed (the family said no cover is needed), idea (a saved option, or a followed session, is available that day and not hidden in the day's marks; title names the first), else open."},"source":{"anyOf":[{"enum":["booking","external","covered","calendar"]},{"type":"null"}],"description":"For a planned day: the covering plan's source (booking: a Hallpass booking; external: a registration made elsewhere; covered: the family's own cover), or calendar for a Google event the family said covers the day. Null otherwise."},"planId":{"anyOf":[{"type":"string"},{"type":"null"}]},"title":{"anyOf":[{"type":"string"},{"type":"null"}]},"activityId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"The covering plan's activity, or the first idea's; null without one."}}},"FamilyPlanCalendarFriend":{"type":"object","required":["userId","publicName","publicNameKnown"],"additionalProperties":false,"properties":{"userId":{"type":"string","format":"uuid"},"publicName":{"type":["string","null"]},"publicNameKnown":{"type":"boolean","description":"False when the parent has no name to show; publicName is then null and the client writes its own wording."}}},"FamilyPlanCalendarEntry":{"type":"object","required":["id","kind","title","startMinutes","endMinutes","planId","eventId","activityId","activity","recurring","friends"],"additionalProperties":false,"properties":{"id":{"type":"string"},"kind":{"enum":["plan","pending","gap","event","flag","school"],"description":"school: the day off or early release. plan: an active plan. pending: a pending or waitlisted plan. gap: a day off no plan covers, over the school's hours (or the default care hours). event: a Google event naming the child. flag: an alert of the day for the child (registration opening, or a spot that opened)."},"title":{"type":"string"},"startMinutes":{"anyOf":[{"type":"integer","minimum":0,"maximum":1440},{"type":"null"}],"description":"Minutes after midnight. A plan's are that day's own: a multi-day booking keeps each day's drop-off and pick-up (dayTimes on the plan), so an overnight stay starts on its first day and ends on its last, with neither between."},"endMinutes":{"anyOf":[{"type":"integer","minimum":0,"maximum":1440},{"type":"null"}]},"planId":{"anyOf":[{"type":"string"},{"type":"null"}]},"eventId":{"anyOf":[{"type":"string"},{"type":"null"}]},"activityId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"activity":{"anyOf":[{"$ref":"#/components/schemas/FamilyPlanActivity"},{"type":"null"}],"description":"On plan and pending entries: the plan's catalog activity (name and photo); null without one, or once it left the catalog."},"recurring":{"type":"boolean","description":"True for a plan that repeats weekly (a plan with a recurrence)."},"friends":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanCalendarFriend"},"description":"On plan and pending entries: friends who share their plans and have a booking or registered plan for the same session."}}},"FamilyPlanCalendarChildDay":{"type":"object","required":["childId","school","schoolLabel","cover","entries","ideas"],"additionalProperties":false,"properties":{"childId":{"type":"string","format":"uuid"},"school":{"enum":["in_session","no_school","early_release","break","none"],"description":"none without a school calendar, outside the school's term and its summer, and on days the school does not meet. Summer around the term (see the school calendar's days) is break."},"schoolLabel":{"anyOf":[{"type":"string"},{"type":"null"}]},"cover":{"$ref":"#/components/schemas/FamilyPlanCalendarCover"},"entries":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanCalendarEntry"},"description":"detail=day only; empty in a summary."},"ideas":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanIdea"},"description":"detail=day only (empty in a summary): the options behind an idea cover; empty for any other cover."}}},"FamilyPlanCalendarAlert":{"type":"object","required":["trackId","title","childIds","kind"],"additionalProperties":false,"properties":{"trackId":{"type":"string"},"title":{"type":"string"},"childIds":{"type":"array","items":{"type":"string","format":"uuid"}},"kind":{"enum":["registration_opens","spot_opens"]}}},"FamilyPlanCalendarFriendOpen":{"type":"object","required":["userId","publicName","publicNameKnown","childFirstName"],"additionalProperties":false,"properties":{"userId":{"type":"string","format":"uuid"},"publicName":{"type":["string","null"]},"publicNameKnown":{"type":"boolean","description":"False when the parent has no name to show; publicName is then null and the client writes its own wording."},"childFirstName":{"type":"null","description":"Always null: an open mark is household-level."}}},"FamilyPlanCalendarDay":{"type":"object","required":["date","weekend","open","children","events","alerts","friendsOpen"],"additionalProperties":false,"properties":{"date":{"type":"string","format":"date"},"weekend":{"type":"boolean"},"open":{"anyOf":[{"enum":["open","not","auto"]},{"type":"null"}],"description":"The family's open mark; unmarked, auto when a child the read covers (every child, or only childId) has a day off that nothing covers yet (open to plans on its own), else null."},"children":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanCalendarChildDay"}},"events":{"type":"array","items":{"$ref":"#/components/schemas/FamilyAccountCalendarEvent"},"description":"detail=day only; empty in a summary."},"alerts":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanCalendarAlert"}},"friendsOpen":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanCalendarFriendOpen"}}}},"FamilyPlanCalendarMonth":{"type":"object","required":["month","booked","toPlan"],"additionalProperties":false,"properties":{"month":{"type":"string","pattern":"^[0-9]{4}-[0-9]{2}$"},"booked":{"type":"integer","minimum":0,"description":"Child-days in the range with planned cover."},"toPlan":{"type":"integer","minimum":0,"description":"Child-days in the range that are gaps with open cover."}}},"FamilyPlanCalendar":{"type":"object","required":["days","months"],"additionalProperties":false,"properties":{"days":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanCalendarDay"}},"months":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanCalendarMonth"}}}},"FamilyPlanHomeAction":{"oneOf":[{"type":"object","required":["kind","bookingId","options"],"additionalProperties":false,"properties":{"kind":{"const":"answer_outcome"},"bookingId":{"type":"string","format":"uuid"},"options":{"type":"array","items":{"enum":["registered","not_registered","not_yet"]}}}},{"type":"object","required":["kind","planId","options"],"additionalProperties":false,"properties":{"kind":{"const":"answer_plan"},"planId":{"type":"string"},"options":{"type":"array","items":{"enum":["registered","applied","canceled"]}}},"description":"PATCH the plan with one of options as its status. For an applied plan, applied is \"not yet\": Home asks again later."},{"type":"object","required":["kind","bookingId"],"additionalProperties":false,"properties":{"kind":{"const":"finish_booking"},"bookingId":{"type":"string","format":"uuid"}}},{"type":"object","required":["kind","sessionId"],"additionalProperties":false,"properties":{"kind":{"const":"book_spot"},"sessionId":{"type":"string","format":"uuid"}}},{"type":"object","required":["kind","bookingId"],"additionalProperties":false,"properties":{"kind":{"const":"refund_choice"},"bookingId":{"type":"string","format":"uuid"}}},{"type":"object","required":["kind","url"],"additionalProperties":false,"properties":{"kind":{"const":"open_provider"},"url":{"type":"string"}}},{"type":"object","required":["kind","date","childIds"],"additionalProperties":false,"properties":{"kind":{"const":"find_options"},"date":{"type":"string","format":"date"},"childIds":{"type":"array","items":{"type":"string","format":"uuid"}}}},{"description":"Complete the child's profile.","type":"object","required":["kind","childId"],"additionalProperties":false,"properties":{"kind":{"const":"edit_child"},"childId":{"type":"string","format":"uuid"}}},{"description":"Answer the registration form's new required questions.","type":"object","required":["kind","registrationId"],"additionalProperties":false,"properties":{"kind":{"const":"answer_form"},"registrationId":{"type":"string","format":"uuid"}}}]},"FamilyPlanHomeItem":{"type":"object","required":["id","kind","date","childIds","title","line","fromEmail","activityId","planIds","actions"],"additionalProperties":false,"properties":{"id":{"type":"string","pattern":"^[a-z_]+:[A-Za-z0-9-]{1,80}$","description":"Stable \"<kind>:<subjectId>\"; dismiss it with E-62."},"kind":{"enum":["canceled_by_provider","confirm_registration","spot_opened","finish_booking","missing_detail","due_on_provider_site","conflict","email_question"],"description":"spot_opened: a session the family is waitlisted for or follows shows open places (book_spot). missing_detail: a child with upcoming plans lacks profile details a booking sends (edit_child), or a live registration lacks answers its form now requires (answer_form)."},"date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}]},"childIds":{"type":"array","items":{"type":"string","format":"uuid"}},"title":{"type":"string"},"line":{"type":"string"},"fromEmail":{"type":"boolean"},"activityId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"planIds":{"type":"array","items":{"type":"string","pattern":"^(booking|plan):[0-9a-f-]{36}$"},"description":"The plans the item is about: a booking's (and a waitlisted booking's open spot), an unconfirmed plan's, or both plans of a conflict. Empty otherwise."},"actions":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanHomeAction"}}}},"FamilyPlanHomeNextToPlan":{"type":"object","required":["key","dates","breakName","childIds","state","ideas"],"additionalProperties":false,"properties":{"key":{"type":"string","description":"The break id, or the date of a single day off."},"dates":{"type":"array","items":{"type":"string","format":"date"}},"breakName":{"anyOf":[{"type":"string"},{"type":"null"}]},"childIds":{"type":"array","items":{"type":"string","format":"uuid"}},"state":{"enum":["open","idea"]},"ideas":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanIdea"},"description":"The options the group's days offer, in day order; empty for an open group."}}},"FamilyPlanHomeUpdate":{"type":"object","required":["id","at","text","detail","fromEmail","undoable","undone","undoneText"],"additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"at":{"type":"string","format":"date-time"},"text":{"type":"string"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}]},"fromEmail":{"type":"boolean"},"undoable":{"type":"boolean"},"undone":{"type":"boolean"},"undoneText":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"For an undone update: the sentence to show in its place. Null otherwise."}}},"FamilyPlanHomeRegistrationAlert":{"type":"object","required":["trackId","organizationId","organization","activityId","title","season","opensOn"],"additionalProperties":false,"properties":{"trackId":{"type":"string"},"organizationId":{"type":"string","format":"uuid","description":"The camp the alert is about; link here when `activityId` is null."},"organization":{"anyOf":[{"$ref":"#/components/schemas/FamilyPlanOrganization"},{"type":"null"}],"description":"The camp's name and logo; null once the camp left the catalog."},"activityId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"The followed activity; null when the family follows the whole camp."},"title":{"type":"string"},"season":{"anyOf":[{"type":"string"},{"type":"null"}]},"opensOn":{"type":"string","format":"date"}},"description":"A followed activity or camp whose registration opens soon. A followed camp’s opening has no activity."},"FamilyPlanHomeWaitlist":{"type":"object","required":["bookingId","position","text"],"additionalProperties":false,"properties":{"bookingId":{"type":"string","format":"uuid"},"position":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}]},"text":{"type":"string"}}},"FamilyPlanHome":{"type":"object","required":["needsYou","nextToPlan","updates","registrationAlerts","waitlist","featuredBookingId","friends"],"additionalProperties":false,"properties":{"needsYou":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanHomeItem"}},"nextToPlan":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanHomeNextToPlan"}},"updates":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanHomeUpdate"}},"registrationAlerts":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanHomeRegistrationAlert"}},"waitlist":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanHomeWaitlist"}},"featuredBookingId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"friends":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanHomeFriend"},"description":"Friends who share plans with the family, newest friendship first, at most a dozen (policy)."}}},"FamilyBookingApplied":{"description":"The camp received an application and answers later. Only the service fee is charged (`payment.serviceFeeCents`); the rest of any hold is released. Ask the parent with the outcome route.","allOf":[{"$ref":"#/components/schemas/FamilyBookingCommon"},{"type":"object","required":["phase","status","decision","receipt","failure","checkingWith"],"properties":{"phase":{"type":"string","const":"applied"},"status":{"type":"string","enum":["applied"],"x-open-enum":true},"decision":{"type":"null"},"receipt":{"$ref":"#/components/schemas/BookingReceipt"},"failure":{"type":"null"},"checkingWith":{"type":"null"}}}]},"FamilyWallet":{"type":"object","required":["balanceCents","currency"],"properties":{"balanceCents":{"type":"integer","minimum":0,"description":"What School’s Out owes the family, in cents."},"currency":{"const":"USD"}}},"FamilyWalletEntry":{"type":"object","required":["id","kind","label","detail","amountCents","method","bookingId","occurredAt"],"properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["top_up","refund","booking_payment","booking_hold","hold_released","adjustment","gift_card","referral","group_rate"],"x-open-enum":true},"label":{"type":"string","examples":["Credits added","Refund · Robotics Week","Gift card redeemed","Referral credit"]},"detail":{"type":["string","null"],"description":"“Camp · Child” for a booking entry."},"amountCents":{"type":"integer","description":"Signed from the family’s side: positive adds to the wallet, negative spends or holds."},"method":{"oneOf":[{"type":"object","required":["brand","last4"],"properties":{"brand":{"type":"string"},"last4":{"type":"string","pattern":"^[0-9]{4}$"}}},{"const":"wallet"},{"type":"null"}],"description":"The card of a top-up, `wallet` for a booking entry the wallet paid or got back, else `null` (a group rate credit is given, not paid)."},"bookingId":{"type":["string","null"],"format":"uuid"},"occurredAt":{"type":"string","format":"date-time"}}},"FamilyPayment":{"type":"object","description":"One ledger transaction that moved the family’s money, by card, wallet or both.","required":["id","kind","label","detail","cardCents","walletCents","currency","card","serviceFeeCents","bookingId","giftCardId","occurredAt"],"properties":{"id":{"type":"string","format":"uuid","description":"The ledger transaction."},"kind":{"type":"string","enum":["booking_payment","refund","top_up","gift_card_purchase","gift_card_redeemed","referral","adjustment","group_rate"],"x-open-enum":true},"label":{"type":"string","examples":["Paid · Robotics Week","Refund · Robotics Week","Credits added","Gift card bought","Gift card redeemed","Referral credit"]},"detail":{"type":["string","null"],"description":"“Camp · Child” for a booking’s payment or refund; null otherwise."},"cardCents":{"type":["integer","null"],"description":"What moved on the card, signed from the family’s side: negative is a charge, positive a refund. Null when no card was involved."},"walletCents":{"type":["integer","null"],"description":"What moved in the wallet, signed from the family’s side: positive is a credit, negative spends credits. Null when the wallet was not involved."},"currency":{"type":"string","pattern":"^[A-Z]{3}$"},"card":{"oneOf":[{"type":"object","required":["brand","last4"],"properties":{"brand":{"type":"string"},"last4":{"type":"string","pattern":"^[0-9]{4}$"}}},{"type":"null"}],"description":"The card charged or refunded: for a top-up or a gift card purchase the card it charged, for a booking's card payment or card refund the card its hold was placed on. Null when no card was involved."},"serviceFeeCents":{"type":["integer","null"],"minimum":0,"description":"The service fee within this payment: what a booking payment charged for it, or what a refund returned of it. Null when the payment holds none."},"bookingId":{"type":["string","null"],"format":"uuid"},"giftCardId":{"type":["string","null"],"format":"uuid","description":"The gift card bought or redeemed."},"occurredAt":{"type":"string","format":"date-time"}}},"FamilyBookingStaffFollowUp":{"type":"object","description":"A booking whose money Stripe refused to move after the provider side was settled.","required":["bookingId","ownerUserId","reason","status","paymentStatus","paymentIntentId","refund","cardPaidCents","currency","organizationName","sessionName","updatedAt"],"properties":{"bookingId":{"type":"string","format":"uuid"},"ownerUserId":{"type":["string","null"],"format":"uuid","description":"Null when the family’s account was deleted."},"reason":{"type":"string","enum":["card_refund_refused","card_capture_refused","payment_deferred"],"x-open-enum":true,"description":"`card_refund_refused`: refund the family by hand. `card_capture_refused`: the provider was paid and the family card could not be charged; collect from the family. `payment_deferred`: the camp deferred payment and the family's hold (and any credits held for the booking) is kept; release it, or capture it once School's Out has paid the camp."},"status":{"type":"string"},"paymentStatus":{"type":"string"},"paymentIntentId":{"type":["string","null"],"description":"The family card’s Stripe PaymentIntent."},"refund":{"oneOf":[{"type":"object","required":["method","amountCents","walletCents","status"],"properties":{"method":{"type":"string","enum":["credits","card"]},"amountCents":{"type":"integer","minimum":1,"description":"Everything the family paid, refunded."},"walletCents":{"type":"integer","minimum":0,"description":"The part returned to the wallet at once: all of it for `credits`, the credits part for `card`."},"status":{"type":"string","enum":["pending","done"],"description":"`pending` until the card refund is confirmed."}}},{"type":"null"}]},"cardPaidCents":{"type":"integer","minimum":0,"description":"What the card paid at capture; a card refund returns at most this."},"currency":{"type":"string"},"organizationName":{"type":["string","null"]},"sessionName":{"type":["string","null"]},"updatedAt":{"type":"string","format":"date-time"}}},"FamilyBookingStaffResolution":{"type":"object","description":"How staff resolved a booking whose money waited on them.","required":["bookingId","reason","resolution","note"],"properties":{"bookingId":{"type":"string","format":"uuid"},"reason":{"type":"string","enum":["card_refund_refused","card_capture_refused","payment_deferred"],"x-open-enum":true,"description":"`card_refund_refused`: refund the family by hand. `card_capture_refused`: the provider was paid and the family card could not be charged; collect from the family. `payment_deferred`: the camp deferred payment and the family's hold (and any credits held for the booking) is kept; release it, or capture it once School's Out has paid the camp."},"resolution":{"type":"string","enum":["refunded","collected","written_off","hold_released","hold_captured"],"x-open-enum":true},"note":{"type":"string"}}},"FamilyPaymentMethodCard":{"type":"object","required":["id","brand","last4","expMonth","expYear","name"],"properties":{"id":{"type":"string","pattern":"^pm_"},"brand":{"type":"string"},"last4":{"type":"string","pattern":"^[0-9]{4}$"},"expMonth":{"type":"integer","minimum":0,"maximum":12,"description":"0 until the next sync reads it."},"expYear":{"type":"integer","minimum":0},"name":{"type":["string","null"],"description":"The cardholder’s name Stripe has for the card (billing_details.name, as Stripe Elements sends it); null when it has none or Stripe was not read."}}},"FamilyPaymentMethodBillingAddress":{"type":"object","required":["line1","city","region","postalCode"],"properties":{"line1":{"type":"string","minLength":1,"maxLength":200},"city":{"type":"string","minLength":1,"maxLength":100},"region":{"type":"string","pattern":"^[A-Z]{2}$"},"postalCode":{"type":"string","pattern":"^[0-9]{5}$"}}},"FamilyPaymentMethods":{"type":"object","required":["paymentMethod","default","backup","cards","billingAddress","receiptEmail"],"properties":{"paymentMethod":{"oneOf":[{"$ref":"#/components/schemas/BookingPaymentMethod"},{"type":"null"}],"description":"The default card’s brand and last4; kept for apps that read only it."},"default":{"oneOf":[{"$ref":"#/components/schemas/FamilyPaymentMethodCard"},{"type":"null"}]},"backup":{"oneOf":[{"$ref":"#/components/schemas/FamilyPaymentMethodCard"},{"type":"null"}]},"cards":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPaymentMethodCard"},"description":"Every card saved to the parent’s Stripe customer, newest first, including any in neither slot, which PATCH can make the default or the backup again. When Stripe can’t be read, the default and backup alone."},"billingAddress":{"oneOf":[{"$ref":"#/components/schemas/FamilyPaymentMethodBillingAddress"},{"type":"null"}]},"receiptEmail":{"type":["string","null"],"format":"email","maxLength":254,"description":"Where payment receipts are emailed instead of the account email; null sends them to the account email."}}},"FamilyBookingCancelInput":{"type":"object","additionalProperties":false,"properties":{"refundMethod":{"type":"string","enum":["credits","card"]},"reason":{"type":"string","enum":["plans_changed","sick","found_another","schedule_conflict","other"]}}},"BookingCancellationAction":{"type":"string","enum":["cancel","choose_refund"],"description":"What POST .../cancel does with this booking. cancel: stops it and releases anything held. choose_refund: the camp cancelled it; the parent chooses how the payment comes back (refundMethod)."},"BookingCancellationBlock":{"type":"string","enum":["checkout_started","checking_with_camp","booked","waitlisted","ended","cancel_requested","refund_chosen","nothing_paid"],"description":"Why POST .../cancel refuses this booking. booked and waitlisted: parents can't cancel a place here yet (show \"contact us\"); the group cancellation rules will allow it up to a deadline. checkout_started: the camp's checkout has begun paying. checking_with_camp: School's Out is checking the outcome with the camp. ended: already failed or cancelled. refund_chosen and nothing_paid: the camp cancelled and there is no refund left to choose. cancel_requested: a cancel is already on its way; sending it again returns the booking unchanged (`200`), with no second request."},"FamilyBookingCancellation":{"type":"object","additionalProperties":false,"required":["allowed","action","reason","deadline","refundableCents","methods"],"description":"What POST .../cancel accepts for this booking right now, decided by the same code the route runs, so a Cancel button shown from it never meets a 409 unless the booking moved on. It widens when the group cancellation rules land (booked bookings up to a deadline).","properties":{"allowed":{"type":"boolean"},"action":{"$ref":"#/components/schemas/BookingCancellationAction"},"reason":{"anyOf":[{"$ref":"#/components/schemas/BookingCancellationBlock"},{"type":"null"}],"description":"null exactly when allowed."},"deadline":{"type":["string","null"],"format":"date-time","description":"The last moment the route accepts it; null while no rule sets one (always, today)."},"refundableCents":{"type":"integer","minimum":0,"description":"What the route refunds: everything the family paid for choose_refund, 0 for cancel (which releases holds and charges nothing)."},"methods":{"type":"array","items":{"type":"string","enum":["credits","card"]},"description":"The refundMethod values the route acts on; empty when it refunds nothing."}}},"FamilyBookingChangeInput":{"type":"object","additionalProperties":false,"required":["idempotencyKey","sessionId"],"properties":{"idempotencyKey":{"type":"string","format":"uuid","description":"One per change request; resending it returns the same new booking with `200`."},"sessionId":{"type":"string","format":"uuid","description":"Another open session of the same activity."},"maxSpend":{"$ref":"#/components/schemas/BookingMoney","description":"Defaults to the original spending limit."},"refundMethod":{"type":"string","enum":["credits","card"],"description":"How the original’s payment comes back once the change completes; default `card`."}},"example":{"idempotencyKey":"3e9a1c7b-5d2f-4b8e-a6c4-0f1e2d3c4b5a","sessionId":"d4b8f2a1-7c3e-4f9d-8a6b-1e2f3c4d5a6b","refundMethod":"credits"}},"FamilyBookingOutcomeInput":{"type":"object","additionalProperties":false,"required":["outcome"],"properties":{"outcome":{"type":"string","enum":["registered","not_registered","not_yet","provider_task_done"],"description":"`registered`: the application became a place (applied → booked, `receipt.source: parent`). `not_registered`: no place (applied → not_booked with failure `application_declined`; any hold is released). `not_yet`: ask again in a few days (`askAgainAfter`). `provider_task_done`: the parent finished `dueOnProviderSite`, which is cleared."}}},"FamilyWalletTopUpInput":{"type":"object","additionalProperties":false,"required":["idempotencyKey","amountCents"],"properties":{"idempotencyKey":{"type":"string","format":"uuid","description":"One per top-up. Resend it after a `503`, or after the parent completes authentication, to finish the same top-up; with different details it returns `409`."},"amountCents":{"type":"integer","minimum":1000,"maximum":50000},"paymentMethod":{"type":"string","enum":["default","backup"],"description":"Optional. Which of the parent’s saved cards to charge; defaults to the default card."}},"example":{"idempotencyKey":"6c5b4a39-2817-4f6e-9d5c-4b3a29180f7e","amountCents":2500,"paymentMethod":"default"}},"FamilyGiftCard":{"type":"object","additionalProperties":false,"required":["id","amountCents","currency","status","codeLast4","recipientName","recipientEmail","emailStatus","message","card","purchasedAt","redeemedAt"],"description":"A gift card the viewer bought. The code itself is shown only once, in the purchase response; this shows its last four characters.","properties":{"id":{"type":"string","format":"uuid"},"amountCents":{"type":"integer","minimum":1},"currency":{"type":"string","const":"USD"},"status":{"type":"string","enum":["active","redeemed"],"description":"`active` until someone redeems the code into their wallet. Gift cards do not expire."},"codeLast4":{"type":"string","pattern":"^[2-9A-HJKMNP-Z]{4}$"},"recipientName":{"type":["string","null"],"maxLength":100},"recipientEmail":{"type":["string","null"],"format":"email","description":"Where School's Out emails the code; null when the purchaser shares it themselves."},"emailStatus":{"type":"string","enum":["none","pending","sent","failed"],"description":"Delivery of the code to `recipientEmail`: `none` without one, `pending` while queued or retrying, `sent`, or `failed` after the last retry. Replaying the purchase issues a new code and emails it again."},"message":{"type":["string","null"],"maxLength":300},"card":{"type":"object","additionalProperties":false,"required":["brand","last4"],"properties":{"brand":{"type":"string"},"last4":{"type":"string","pattern":"^[0-9]{4}$"}},"description":"The purchaser's saved card that paid."},"purchasedAt":{"type":"string","format":"date-time"},"redeemedAt":{"type":["string","null"],"format":"date-time"}},"example":{"id":"5b0f8d2e-3c41-4b8a-9e6f-2d7a1c9b4e30","amountCents":5000,"currency":"USD","status":"active","codeLast4":"Q7XM","recipientName":"Jordan","recipientEmail":"jordan@example.com","emailStatus":"pending","message":"Happy summer!","card":{"brand":"visa","last4":"4242"},"purchasedAt":"2026-10-16T17:04:11Z","redeemedAt":null}},"FamilyGiftCardPurchaseInput":{"type":"object","additionalProperties":false,"required":["idempotencyKey","amountCents"],"properties":{"idempotencyKey":{"type":"string","format":"uuid","description":"One key is one gift card and one card charge. Replaying it after the card is active issues a NEW code and the previous code stops working."},"amountCents":{"type":"integer","minimum":1000,"maximum":50000,"description":"Bounds are policy and may change."},"recipientName":{"type":["string","null"],"maxLength":100},"recipientEmail":{"type":["string","null"],"format":"email","description":"When set, the code is also emailed to this address (with recipientName and message) shortly after the purchase."},"message":{"type":["string","null"],"maxLength":300},"paymentMethod":{"type":"string","enum":["default","backup"],"default":"default","description":"Which of the viewer's own saved cards to charge."}},"example":{"idempotencyKey":"0d6f1c9a-2b7e-4f3d-8a1c-5e9b7d2f4a60","amountCents":5000,"recipientName":"Jordan","recipientEmail":"jordan@example.com","message":"Happy summer!","paymentMethod":"default"}},"FamilyGiftCardIssued":{"type":"object","additionalProperties":false,"required":["giftCard","code"],"properties":{"giftCard":{"$ref":"#/components/schemas/FamilyGiftCard"},"code":{"type":"string","pattern":"^[2-9A-HJKMNP-Z]{4}(-[2-9A-HJKMNP-Z]{4}){3}$","description":"The redemption code. Share it with the recipient; it stays readable to you through GET /api/users/viewer/gift-cards/{giftCardId}/code."}},"example":{"giftCard":{"id":"5b0f8d2e-3c41-4b8a-9e6f-2d7a1c9b4e30","amountCents":5000,"currency":"USD","status":"active","codeLast4":"Q7XM","recipientName":"Jordan","recipientEmail":"jordan@example.com","emailStatus":"pending","message":"Happy summer!","card":{"brand":"visa","last4":"4242"},"purchasedAt":"2026-10-16T17:04:11Z","redeemedAt":null},"code":"K7MD-3QPA-WX9H-Q7XM"},"description":"A gift card you bought with its redemption code. The code is stored sealed, so it can be shown to you again, before and after it is redeemed; answers carrying it are never cached."},"FamilyGiftCardRedeemInput":{"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string","description":"The 16-character code; case, spaces and hyphens are ignored."}},"example":{"code":"k7md-3qpa-wx9h-q7xm"}},"FamilyFeedCampMention":{"type":"object","additionalProperties":false,"properties":{"organizationId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The camp's name."},"logoUrl":{"type":["string","null"],"description":"The camp's logo; null when none is known. Once rendered it is the logo's public, cross-origin rendition (GET /api/media/logos/{file}), else its source URL."}},"required":["organizationId","name","logoUrl"],"description":"A mention of the post's own camp. A post only ever mentions the camp it is about."},"FamilyFeedLinkTarget":{"description":"A catalog record by id, or an outside page by URL.","oneOf":[{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string","enum":["activity","organization"]},"id":{"type":"string","format":"uuid"}},"required":["kind","id"]},{"type":"object","additionalProperties":false,"properties":{"kind":{"const":"url","type":"string"},"url":{"type":"string","format":"uri"}},"required":["kind","url"]}]},"FamilyFeedLink":{"type":"object","additionalProperties":false,"properties":{"text":{"type":"string"},"target":{"$ref":"#/components/schemas/FamilyFeedLinkTarget"}},"required":["text","target"]},"FamilyFeedRun":{"description":"One piece of a paragraph: plain text, a mention of the post's own camp, or a link.","oneOf":[{"type":"object","additionalProperties":false,"properties":{"text":{"type":"string"}},"required":["text"]},{"type":"object","additionalProperties":false,"properties":{"camp":{"$ref":"#/components/schemas/FamilyFeedCampMention"}},"required":["camp"]},{"type":"object","additionalProperties":false,"properties":{"link":{"$ref":"#/components/schemas/FamilyFeedLink"}},"required":["link"]}]},"FamilyFeedImage":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string","description":"An image of the post's own activity or camp; a relative path for an uploaded brand image."},"alt":{"type":"string"},"position":{"type":["string","null"],"description":"A CSS object-position for cropping; null when unspecified."}},"required":["url","alt","position"]},"FamilyFeedPost":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["pick","camp","friend","local","event"]},"topic":{"type":"string","description":"A topic mark the app can draw."},"headline":{"type":"string"},"body":{"type":"array","description":"Paragraphs, each an array of runs.","items":{"type":"array","items":{"$ref":"#/components/schemas/FamilyFeedRun"}}},"image":{"description":"The post's photo: its activity's first photo (cover, else first gallery photo), else its camp's, else its pool item's own image. A logo is never a post's photo (a camp mention carries it as logoUrl), so a post without a photo has null and the app fills the slot.","oneOf":[{"type":"null"},{"$ref":"#/components/schemas/FamilyFeedImage"}]},"until":{"type":"string","format":"date","description":"The last family-local date the post is relevant: an event's or closure's end, or the offering's next session start."},"reason":{"type":"string","description":"Why the post is in the feed, one line."},"signal":{"type":["string","null"],"enum":["like","dislike","dont_show_again",null]},"done":{"type":"boolean","description":"Whether the viewer marked the post done in Plans' To do (PUT /api/users/viewer/feed/posts/{postId}/done)."},"organizationId":{"type":["string","null"],"format":"uuid","description":"The camp the post is about."},"activityId":{"type":["string","null"],"format":"uuid","description":"The activity the post is about."}},"required":["id","kind","topic","headline","body","image","until","reason","signal","done","organizationId","activityId"]},"FamilyFeedEdition":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"date":{"type":"string","format":"date","description":"The family-local date."},"slot":{"type":"string","enum":["morning","evening"]},"label":{"type":"string","description":"The edition's name in the household time zone, for example \"Monday morning\"."},"publishedAt":{"type":"string","format":"date-time"},"posts":{"type":"array","items":{"$ref":"#/components/schemas/FamilyFeedPost"}}},"required":["id","date","slot","label","publishedAt","posts"]},"FamilyFeedNote":{"type":"object","additionalProperties":false,"description":"The adult's daily note: one line that sets the mood for the day, a credited quote from a classic book or a small true fact, picked from the feed's pool on the adult's first read of the family-local date and the same on every read that date. greeting and pose follow the time of the read; the line does not.","properties":{"date":{"type":"string","format":"date","description":"The family-local date the line is for."},"id":{"type":"string","description":"The pool line's permanent id, or staff for a line staff wrote for the family's area and date."},"type":{"type":"string","enum":["quote","fact","welcome","staff"],"description":"quote (a short quote from a book first published before 1930, always shown with its credit), fact (a small true fact), welcome (while the adult has no children set up), or staff (a line staff wrote for the family's area and date)."},"greeting":{"type":"string","description":"The card's header for the time it is now in the family's time zone: Good morning, Good afternoon, Good evening, or on a weekend Good Saturday morning."},"text":{"type":"string","description":"The line: at most about 25 words and two sentences, no emoji."},"credit":{"type":["string","null"],"description":"A quote's author and book, shown under it: \"Lewis Carroll, Through the Looking-Glass\". Null for a fact, a welcome line, or a staff line."},"doodle":{"type":"string","enum":["air","art","animals","festival","food","general","library","music","park","rain","roads","school","science","seasonal","snow","sports","sun","theater","transit","water"],"description":"The illustration drawn beside the line, one of the feed's topic marks."},"pose":{"$ref":"#/components/schemas/FamilyFeedNotePose"},"seenAt":{"type":["string","null"],"format":"date-time","description":"When the adult first marked it seen."},"savedAt":{"type":["string","null"],"format":"date-time","description":"When the adult saved it to their Notes; null unless saved."}},"required":["date","id","type","greeting","text","credit","doodle","pose","seenAt","savedAt"]},"FamilyFeed":{"type":"object","additionalProperties":false,"properties":{"edition":{"description":"The current edition, also among editions.","$ref":"#/components/schemas/FamilyFeedEdition"},"editions":{"description":"The current edition's day: its morning edition, then its evening edition. Both are published on the day's first read, so the evening is out before it becomes current.","type":"array","minItems":2,"maxItems":2,"items":{"$ref":"#/components/schemas/FamilyFeedEdition"}},"note":{"$ref":"#/components/schemas/FamilyFeedNote"}},"required":["edition","editions","note"]},"FamilyNotificationTarget":{"description":"Where opening the notification leads.","oneOf":[{"type":"object","additionalProperties":false,"properties":{"kind":{"const":"booking","type":"string"},"bookingId":{"type":"string","format":"uuid"}},"required":["kind","bookingId"]},{"type":"object","additionalProperties":false,"properties":{"kind":{"const":"activity","type":"string"},"activityId":{"type":"string","format":"uuid"},"sessionId":{"type":["string","null"],"format":"uuid"}},"required":["kind","activityId","sessionId"]},{"type":"object","additionalProperties":false,"properties":{"kind":{"const":"organization","type":"string"},"organizationId":{"type":"string","format":"uuid"}},"required":["kind","organizationId"]},{"type":"object","additionalProperties":false,"properties":{"kind":{"const":"person","type":"string"},"userId":{"type":"string","format":"uuid"}},"required":["kind","userId"]},{"type":"object","additionalProperties":false,"properties":{"kind":{"const":"collection","type":"string"},"collectionId":{"type":"string","format":"uuid"}},"required":["kind","collectionId"]},{"type":"object","additionalProperties":false,"description":"A question about one of the family's plans (did you get a spot, or confirm a plan found in a registration email): open it on Plans (Home).","properties":{"kind":{"const":"plan","type":"string"},"planId":{"type":"string","format":"uuid"}},"required":["kind","planId"]},{"type":"object","additionalProperties":false,"properties":{"kind":{"const":"screen","type":"string"},"screen":{"type":"string","enum":["home","wallet","family","settings"]}},"required":["kind","screen"]},{"type":"object","required":["kind","event"],"properties":{"kind":{"const":"family","type":"string"},"event":{"type":"string","enum":["member_joined","invitation_declined","member_removed","role_changed","new_invite_requested"]},"userId":{"type":"string","format":"uuid","description":"The adult the event is about, when they have an account."},"invitationId":{"type":"string","format":"uuid","description":"The invitation the event is about."}},"additionalProperties":false,"description":"A change to the family's members (an account notification): open the family's members."}]},"FamilyNotification":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["booking","waitlist","friend","alert","account"]},"title":{"type":"string"},"body":{"type":"string"},"target":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/FamilyNotificationTarget"}]},"createdAt":{"type":"string","format":"date-time"},"readAt":{"type":["string","null"],"format":"date-time"}},"required":["id","kind","title","body","target","createdAt","readAt"]},"FamilyNotificationChannels":{"type":"object","additionalProperties":false,"properties":{"email":{"type":"boolean"},"push":{"type":"boolean"},"sms":{"type":"boolean"}},"required":["email","push","sms"],"description":"Whether each channel is on. Push goes to the family's signed-in browsers that registered a push subscription."},"FamilyNotificationPreferenceValues":{"type":"object","additionalProperties":false,"properties":{"channels":{"type":"object","additionalProperties":false,"properties":{"bookings":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"waitlist":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"schedule":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"groups":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"friends":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"assistant":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"receipts":{"$ref":"#/components/schemas/FamilyNotificationEmailChannel"}},"required":["bookings","waitlist","schedule"],"description":"Every channel of bookings, waitlist and schedule. `groups`, `friends`, `assistant` and `receipts` may be left out, each keeping its saved value (its default for a family that never set it)."},"quietHours":{"$ref":"#/components/schemas/FamilyNotificationQuietHours"},"productNews":{"type":"boolean","description":"Whether the family gets School's Out's marketing email (Product news), apart from product notifications. Stored only: nothing sends it yet."}},"required":["channels"],"description":"`quietHours` and `productNews` may be left out, keeping their saved values. Any other key is rejected."},"FamilyNotificationPreferences":{"type":"object","additionalProperties":false,"properties":{"channels":{"type":"object","additionalProperties":false,"properties":{"bookings":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"waitlist":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"schedule":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"groups":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"friends":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"assistant":{"$ref":"#/components/schemas/FamilyNotificationChannels"},"receipts":{"$ref":"#/components/schemas/FamilyNotificationEmailChannel"}},"required":["bookings","waitlist","schedule","groups","friends","assistant","receipts"],"description":"Each notification row’s channels, and `receipts`: whether payment receipts are emailed (email only; on unless the family turns it off). Nothing publishes under `groups`, `friends` or `assistant` yet."},"quietHours":{"$ref":"#/components/schemas/FamilyNotificationQuietHours"},"productNews":{"type":"boolean","description":"Whether the family gets School's Out's marketing email (Product news), apart from product notifications. Stored only: nothing sends it yet."},"version":{"type":"integer","minimum":0,"description":"0 until the family first saves; a family at version 0 sees the defaults."}},"required":["channels","quietHours","productNews","version"]},"FamilyPushSubscriptionInput":{"type":"object","additionalProperties":false,"description":"A browser's PushSubscription.toJSON().","properties":{"endpoint":{"type":"string","format":"uri","maxLength":2048,"description":"The browser's push service URL (https, at a known browser push service)."},"expirationTime":{"type":["number","null"],"description":"Accepted and not kept."},"keys":{"$ref":"#/components/schemas/FamilyPushSubscriptionKeys"}},"required":["endpoint","keys"]},"FamilyPushSubscriptionKeys":{"type":"object","additionalProperties":false,"properties":{"p256dh":{"type":"string","description":"base64url uncompressed P-256 public key (65 bytes)."},"auth":{"type":"string","description":"base64url authentication secret (16 bytes)."}},"required":["p256dh","auth"]},"FamilyPushSubscription":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"endpoint":{"type":"string"},"createdAt":{"type":"string","format":"date-time","description":"When the sign-in session first subscribed."},"updatedAt":{"type":"string","format":"date-time","description":"When the sign-in session last subscribed."}},"required":["id","endpoint","createdAt","updatedAt"]},"FeedDailyNote":{"type":"object","additionalProperties":false,"properties":{"area":{"type":"object","additionalProperties":false,"properties":{"datasetId":{"type":"string"},"kind":{"type":"string","enum":["state","zip","metro","county","city","neighborhood"]},"code":{"type":"string"}},"required":["datasetId","kind","code"]},"areaName":{"type":"string"},"date":{"type":"string","format":"date"},"text":{"type":"string"},"writtenBy":{"type":"string","enum":["staff","model"]},"createdAt":{"type":"string","format":"date-time"}},"required":["area","areaName","date","text","writtenBy","createdAt"]},"FamilyCatalogAvailability":{"description":"Derived on the household-local date: closed after registration closes, opens_on before it opens, then registration_status (the enum open, waitlist or closed, as the scores read it), falling back to a stated registration window when the status is unknown. An open session with known capacity and enrollment is full (waitlist when it keeps one) without spots and limited with few left. null when unknown. Capacity and enrollment numbers are never returned.","enum":["open","limited","waitlist","full","closed","opens_on",null],"type":["string","null"]},"FamilyCatalogNextSession":{"type":"object","required":["id","startDate","endDate","startTime","endTime","availability","spotsLeft","registrationUrl","price","priceCurrency","priceBasis","daysOfWeek","registrationMode","agentBookable","bookingPlatform"],"properties":{"id":{"type":"string","format":"uuid"},"startDate":{"type":["string","null"],"format":"date"},"endDate":{"type":["string","null"],"format":"date"},"startTime":{"type":["string","null"]},"endTime":{"type":["string","null"]},"availability":{"$ref":"#/components/schemas/FamilyCatalogAvailability"},"registrationUrl":{"description":"Where a family registers for this session, as a session row's registrationUrl.","type":["string","null"],"format":"uri"},"price":{"description":"The session's sole stated price (session_prices), as priceMax compares it; null when it states none or several quotes.","type":["number","null"]},"priceCurrency":{"description":"That price's currency, USD when unstated; null without a price.","type":["string","null"]},"priceBasis":{"description":"What that price buys, as priceMax reads it: session (the whole enrollment) when unstated; null without a price.","enum":["hour","day","week","month","session",null],"type":["string","null"]},"daysOfWeek":{"description":"The weekdays the schedule covers, in week order, as the weekends filter reads them (session_filter_facts.weekdays): the weekdays of its attended dates, plus every day a date_range spans, so a range of a week or more, or an ongoing one, covers all seven. Empty when the schedule states no day.","type":"array","items":{"type":"string","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]}},"spotsLeft":{"type":["integer","null"],"minimum":1,"description":"Sessions only: the places left, set exactly when availability is limited (at or below its threshold), else null. Raw capacity and enrollment are never returned."},"registrationMode":{"$ref":"#/components/schemas/FamilyRegistrationMode"},"agentBookable":{"$ref":"#/components/schemas/FamilyAgentBookable"},"bookingPlatform":{"type":["string","null"],"description":"Sessions only: the booking platform the session's booking target (the URL Hallpass opens, as agentBookable reads it) is on (Amilia, Sawyer, ...); null without a target or on no known platform. It can differ from registrationUrl's site, which may be a supporting link."}}},"FamilyCatalogRecord":{"description":"The family fields of a record, added to the reader's tier fields (alone below Starter). They show identity, description, ages and grades, address and coordinates, categories, care, schedule, price facts, registration status and dates, the cancellation policy, accessibility and amenities, image_urls (photos only) and an organization's logo_url, and the related parent summaries, and link out through websiteUrl and registrationUrl. They never add scores, ratings, bookkeeping or provenance, notes, flags, the official or supporting URL columns, or business contact fields: those show only where the reader's tier shows them. No family read shows capacity, current_enrollment or a source_url in nested JSON at any tier (spotsLeft is the one count it reads). The page reads (verification, locations, formatKind, contentUpdatedAt, priceTier, familiesBooked, the booking signals and more) are derived per read; list rows carry familiesBooked, priceTier and contentUpdatedAt only when fields names them. Prose keeps contact scrubbing unless the tier shows it unscrubbed.","type":"object","additionalProperties":true,"properties":{"distanceMiles":{"description":"Spheroid miles from near; present only when near is given; null without coordinates, or unless the row's point is street-grade (coordinate_precision exact, building, parcel or street): a ZIP or state centre, unknown or no stated precision still matches radiusMiles, but its distance would be invented.","type":["number","null"]},"websiteUrl":{"description":"The camp's website: the organization itself, or the one an activity or session names. Its configuration's verified canonical_url, else the first http(s) address among its supporting links (where the imported website is kept). An absolute http(s) URL, or null when none is known; stored addresses that are not URLs are skipped.","type":["string","null"],"format":"uri"},"registrationUrl":{"description":"Sessions only: where a family registers on the camp's own site (the Book on their site button): the session's link (its verified page, else its first supporting http(s) link), else its activity's, else the camp's website. An absolute http(s) URL, or null when none is known; stored addresses that are not URLs are skipped.","type":["string","null"],"format":"uri"},"availability":{"$ref":"#/components/schemas/FamilyCatalogAvailability"},"nextSessions":{"description":"Activities only: up to 3 upcoming live sessions (not ended on the household-local today; a session without a stated end is ongoing) meeting the session filters, by start date.","type":"array","maxItems":3,"items":{"$ref":"#/components/schemas/FamilyCatalogNextSession"}},"interests":{"description":"Activities only: the feed interest ids the activity is tagged with, sorted: exactly the interest values that keep it (feed_activity_interest_tags, the interest filter's and facet's ids).","type":"array","items":{"type":"string"}},"programs":{"description":"Activities only: the program buckets the activity is in: exactly the program values that keep it (activity_filter_facts.program_buckets).","type":"array","items":{"type":"string","enum":["after_school","camps","classes"]}},"sessionsTotal":{"description":"Organization and activity detail only: how many live sessions the record has; sessions lists the first sessionsLimit of them, each with its availability, spotsLeft, registrationUrl, bookingPlatform, registrationMode and agentBookable.","type":"integer","minimum":0},"activitiesTotal":{"description":"Organization detail only: how many live activities the organization has; activities lists the first activitiesLimit of them.","type":"integer","minimum":0},"minimumPrice":{"description":"Activities only: the lowest sole quote in dollars among the activity's live sessions (session_filter_facts.price), the price priceMax without priceBasis compares, so priceMax=N keeps the activity exactly when minimumPrice is at most N. null when no session states one.","type":["number","null"]},"priceCurrency":{"description":"Activities only: minimumPrice's currency (USD); null without a price.","type":["string","null"]},"priceBasis":{"description":"Activities only: what minimumPrice buys, session (the whole enrollment) when unstated; null without a price.","enum":["hour","day","week","month","session",null],"type":["string","null"]},"field_resolution":{"$ref":"#/components/schemas/CanonicalFieldResolution","description":"Present when includeResolution=true; current effective state for the row fields allowed by the caller projection."},"field_sources":{"$ref":"#/components/schemas/CanonicalFieldSources","description":"Present when includeSources=true; current effective source identity for the row fields allowed by the caller projection."},"own_values":{"$ref":"#/components/schemas/CanonicalOwnValues","description":"Present when authorized includeOwnValues=true; entity-owned values, including nulls."},"sessions":{"description":"Organization and activity detail only: the first live sessions (FamilyCatalogDetailSession).","type":"array","items":{"$ref":"#/components/schemas/FamilyCatalogDetailSession"}},"verification":{"$ref":"#/components/schemas/FamilyVerification","description":"Organizations, and an activity's host: whether the organization is verified and claimed. Verified means School's Out staff checked it (never the business confirming its own listing); claimed means it has an active member from an approved claim. Never names the claimant, counts members, or says anything of a claim not approved."},"contentUpdatedAt":{"type":["string","null"],"format":"date-time","description":"Organizations, activities and sessions: when the content a family reads of the record last changed: the record, its own configuration and every configuration it inherits a field from (so an activity's moves when the organization field it inherits changes); an organization's also includes its live activities and sessions, its brand palette and its verification, an activity's its sessions'. Writes of the same values, of bookkeeping only, or of who decides a field (override state) never move it. null when no date is known. Detail always; list rows only with fields=contentUpdatedAt."},"formats":{"type":"array","items":{"$ref":"#/components/schemas/FamilyFormatKind"},"description":"Organizations only: the distinct formatKind values of its live activities, in the order day_camp, overnight_camp, class, drop_in, event."},"ageSpan":{"type":["object","null"],"required":["minYears","maxYears"],"properties":{"minYears":{"type":["integer","null"]},"maxYears":{"type":["integer","null"]}},"description":"Organizations only: the span of its live activities' audience ages in whole years, a bound null when some activity's range is open on that side; null when none states ages."},"care":{"type":["object","null"],"required":["before","after"],"properties":{"before":{"type":"boolean"},"after":{"type":"boolean"}},"description":"Organizations only: whether any of its live sessions' schedules offers before or after care, as the care filter reads it."},"priceTier":{"description":"Activities: 1 (lowest) to 4, the quartile of the activity's lowest price per attended day (a class's per meeting) among activities of its formatKind in its metro. Organizations: {low, high}, the span of its activities' tiers. null without a converted USD price, a format kind, a metro, or enough priced activities to rank. Detail always; list rows only with fields=priceTier.","oneOf":[{"type":"integer","minimum":1,"maximum":4},{"type":"object","required":["low","high"],"properties":{"low":{"type":"integer","minimum":1,"maximum":4},"high":{"type":"integer","minimum":1,"maximum":4}}},{"type":"null"}]},"locations":{"type":"array","items":{"$ref":"#/components/schemas/FamilyLocation"},"description":"Organization detail only: where the business meets: its live activities' street-grade venues (and its own address when no activity meets there), one per venue whatever configurations state it, most activities first. A ZIP-precision activity has no location."},"formatKind":{"oneOf":[{"$ref":"#/components/schemas/FamilyFormatKind"},{"type":"null"}],"description":"Activities only: the activity's format in the family vocabulary (public.activity_format_kind of its free-text format and overnight flag); null when the format does not say. The formatKind filter keeps exactly these values."},"locationId":{"type":["string","null"],"format":"uuid","description":"Activities only: the id of the organization location its own point is at (the same id as on the organization's locations); null without its own street-grade point (one it inherits from its organization is not a venue of its own)."},"areaName":{"type":["string","null"],"description":"Activities only: the neighborhood its own street-grade point is in, else its city (without the Census place type); null without its own street-grade point (an inherited one says nothing of where it meets) or outside every area."},"lastSessionEndedOn":{"type":["string","null"],"format":"date","description":"Activities only: the last day of its latest live session that ended before the household-local today; a session still under way never counts. null when none has ended."},"host":{"oneOf":[{"$ref":"#/components/schemas/FamilyActivityHost"},{"type":"null"}],"description":"Activities only: the organization that runs it (id, name, verification), as the activity page's Hosted by reads it, so the page needs no second request."},"spotsLeft":{"type":["integer","null"],"minimum":1,"description":"Sessions only: the places left, set exactly when availability is limited (at or below its threshold), else null. Raw capacity and enrollment are never returned."},"photos":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["url","width","height"],"properties":{"url":{"type":"string","format":"uri","description":"The photo's display URL, as image_urls carries it: its remote URL verbatim, or its organization's brand asset route for an upload."},"width":{"type":["integer","null"],"minimum":1,"description":"Width in pixels, null when unknown."},"height":{"type":["integer","null"],"minimum":1,"description":"Height in pixels, null when unknown."}}},"description":"Activities only: the activity's own gallery photos in display order (cover first), never its organization's; empty when it has none. resolved_cover keeps its own fallback."},"familiesBooked":{"description":"Organization and activity detail, and list rows with fields=familiesBooked: distinct families signed up for the record's sessions this season (household date), counted near the signed-in viewer's area (a coarse cell of the household's search area, never its home) when one is known. null below five families, a privacy floor; above it count is a step, never the exact number.","oneOf":[{"$ref":"#/components/schemas/FamilyFamiliesBooked"},{"type":"null"}]},"registrationMode":{"$ref":"#/components/schemas/FamilyRegistrationMode"},"agentBookable":{"$ref":"#/components/schemas/FamilyAgentBookable"},"bookingPlatform":{"type":["string","null"],"description":"Sessions only: the booking platform the session's booking target (the URL Hallpass opens, as agentBookable reads it) is on (Amilia, Sawyer, ...); null without a target or on no known platform. It can differ from registrationUrl's site, which may be a supporting link."},"logo_url":{"type":["string","null"],"format":"uri","description":"Organizations only (the catalog keeps logos on organizations; an activity page shows its host's): the selected logo: its public, cross-origin rendition (GET /api/media/logos/{file}) once rendered, else its source URL; omitted without one. logoLoadable says which."},"logoLoadable":{"type":"boolean","description":"Organizations only: whether logo_url is the public logo route, whose pixels any origin can read."},"brandColors":{"oneOf":[{"$ref":"#/components/schemas/FamilyBrandColors"},{"type":"null"}],"description":"Organizations only: the resolved brand palette (the business's palette, else brand_color, else colors sampled from the logo)."},"logoGround":{"oneOf":[{"$ref":"#/components/schemas/FamilyLogoGround"},{"type":"null"}],"description":"Organizations only: the ground the logo sits on, as sampled from it."},"contact_email":{"type":["string","null"],"description":"Organizations only: the business's own email, unscrubbed."},"contact_phone":{"type":["string","null"],"description":"Organizations only: the business's own phone, unscrubbed."},"credentials":{"type":["array","null"],"maxItems":12,"description":"Organizations only: credentials School's Out staff checked, each with its checked date (source_url is never shown to families).","items":{"type":"object","required":["kind","label","checked_at"],"properties":{"kind":{"type":"string","enum":["aca_accredited","background_checks","first_aid_cpr","licensed","lifeguard","other"]},"label":{"type":"string"},"issuer":{"type":"string"},"checked_at":{"type":"string","format":"date"}}}},"faqs":{"type":["array","null"],"maxItems":10,"description":"Organizations only: questions parents ask, answered by the business; updated_at is stamped by the database when an entry's question or answer changes.","items":{"type":"object","required":["question","answer","updated_at"],"properties":{"question":{"type":"string","maxLength":200},"answer":{"type":"string","maxLength":2000},"updated_at":{"type":"string","format":"date-time"}}}},"group_size":{"type":["string","null"],"description":"Activities only: the group size as stated."},"highlights":{"type":["array","null"],"maxItems":3,"description":"Organizations only: up to three strengths the business states.","items":{"type":"object","required":["icon","title"],"properties":{"icon":{"type":"string","description":"A site icon key (pipeline.HighlightIcons)."},"title":{"type":"string","maxLength":60}}}},"offers":{"type":["array","null"],"description":"Organizations and activities: what the business does or does not offer, one entry per key in the fixed key order; a missing key is unknown, never 'not offered'. The effective value, merged per key: a key the record states (offered or not) wins, every other key comes from its activity, then its organization; a record that states none inherits nothing.","items":{"$ref":"#/components/schemas/FamilyOffer"}},"overnight":{"type":["boolean","null"],"description":"Activities and sessions: explicit overnight (true) or day (false) status."},"registration_mode":{"type":["string","null"],"enum":["instant","application",null],"description":"Organizations, activities and sessions: the stated registration mode, the effective value (the record's own, else its activity's, else its organization's); null when none states one. A session's registrationMode resolves it with observed bookings: pages read that."},"special_requirements":{"type":["string","null"],"description":"Activities only: requirements as stated (prose, contact-scrubbed)."},"staff_ratio":{"type":["object","null"],"required":["adults","kids"],"properties":{"adults":{"type":"integer","minimum":1},"kids":{"type":"integer","minimum":1}},"description":"Organizations and activities: adults per kids. The effective value: the record's own statement, else its organization's (an activity's)."},"summary":{"type":["string","null"],"maxLength":54,"description":"Organizations only: the business's one line, in its own words; no fallback is synthesized."},"what_to_bring":{"type":["array","null"],"maxItems":20,"items":{"type":"string","maxLength":80},"description":"Activities only: what a child brings, one line each."}}},"FamilyCatalogPin":{"type":"object","required":["id","latitude","longitude","name"],"properties":{"id":{"type":"string","format":"uuid"},"latitude":{"type":"string","description":"Full stored precision."},"longitude":{"type":"string","description":"Full stored precision."},"name":{"type":["string","null"]}}},"FamilyPlanDayTime":{"type":"object","additionalProperties":false,"required":["date","startTime","endTime"],"description":"A plan's own start and end time on one date. A null time is none that day.","properties":{"date":{"type":"string","format":"date"},"startTime":{"anyOf":[{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},{"type":"null"}]},"endTime":{"anyOf":[{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},{"type":"null"}]}}},"SchoolCalendarPublicationInput":{"type":"object","additionalProperties":false,"required":["timeZone","publishedOn","entries"],"description":"The shape of an embedded school-calendars file, validated the same way.","properties":{"sourceId":{"type":"string","format":"uuid","description":"Optional; when sent, the source in the path."},"timeZone":{"type":"string","description":"The district's IANA time zone."},"publishedOn":{"type":"string","format":"date","description":"When the district last updated its calendar. Without a term or entries, the calendar speaks for the July-to-June school year of this date."},"termStart":{"type":"string","format":"date","description":"The school year's first day; send with termEnd or neither."},"termEnd":{"type":"string","format":"date","description":"The school year's last day, on or after termStart."},"entries":{"type":"array","maxItems":500,"description":"Every no_school and early_release day of the calendar. Required: the publication withdraws the source's days in the span that it does not list, so a calendar with none sends [].","items":{"type":"object","additionalProperties":false,"required":["type","startsOn","endsOn","headline"],"properties":{"type":{"enum":["no_school","early_release"]},"startsOn":{"type":"string","format":"date"},"endsOn":{"type":"string","format":"date","description":"On or after startsOn (inclusive)."},"headline":{"type":"string","minLength":1},"endsAt":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","description":"Release time of an early release, when the district gives one."}}}}}},"SchoolCalendarPublication":{"type":"object","additionalProperties":false,"required":["schools","days","withdrawn"],"properties":{"schools":{"type":"integer","minimum":0,"description":"Schools the source serves."},"days":{"type":"integer","minimum":0,"description":"School days written for them."},"withdrawn":{"type":"integer","minimum":0,"description":"Days of the source inside the calendar's span that it no longer lists, removed."}}},"FamilyStaffChargeFollowUp":{"type":"object","additionalProperties":false,"description":"A wallet top-up or gift card purchase whose PaymentIntent Stripe never named within its idempotency window: it was closed as unresolved instead of being resent, and only Stripe can tell whether the card was charged. Look it up there by stripeIdempotencyKey. Staff close it with PATCH /api/admin/family-bookings/charges/{kind}/{id}/follow-up.","required":["kind","id","ownerUserId","stripeIdempotencyKey","amountCents","currency","createdAt","updatedAt"],"properties":{"kind":{"type":"string","enum":["top_up","gift_card"],"x-open-enum":true},"id":{"type":"string","format":"uuid"},"ownerUserId":{"type":["string","null"],"format":"uuid","description":"The family (or gift card purchaser); null once the account is gone."},"stripeIdempotencyKey":{"type":"string","description":"The Idempotency-Key every attempt sent to Stripe: wallet-top-up-<id> or gift-card-<id>."},"amountCents":{"type":"integer","minimum":1},"currency":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time","description":"When it was closed as unresolved."}}},"FamilyStaffChargeResolution":{"type":"object","additionalProperties":false,"description":"How staff closed an unresolved saved-card charge.","required":["kind","id","resolution","note"],"properties":{"kind":{"type":"string","enum":["top_up","gift_card"],"x-open-enum":true},"id":{"type":"string","format":"uuid"},"resolution":{"type":"string","enum":["refunded","written_off","resolved_in_stripe"],"x-open-enum":true},"note":{"type":"string"}}},"SessionPrice":{"additionalProperties":false,"description":"One ordered enrollment option with nullable tuition facts, an optional source-grounded description, and optional before- and after-care quotes. At least one tuition fact or care quote must be known; a description alone is invalid. A care-only option does not establish free or known tuition. No currency, basis, or default description is inferred.","not":{"properties":{"price":{"type":"null"},"basis":{"type":"null"},"currency":{"type":"null"},"before_care_pricing":{"type":"null"},"after_care_pricing":{"type":"null"}}},"properties":{"price":{"description":"Stated amount in major currency units. Zero means explicitly free; null means unknown.","minimum":0,"multipleOf":0.01,"type":["number","null"]},"basis":{"description":"Stated rate basis; session covers the entire enrollment. Null means unknown.","enum":["hour","day","week","month","session",null],"type":["string","null"]},"currency":{"description":"Stated three-letter currency code. Null means unknown.","maxLength":3,"minLength":3,"pattern":"^[A-Za-z]{3}$","type":["string","null"]},"description":{"description":"Optional source-grounded distinction such as Members or Non-members, including for a single option. Surrounding whitespace is trimmed; null or blank text is absent and omitted from responses.","type":["string","null"]},"before_care_pricing":{"$ref":"#/components/schemas/CarePricing"},"after_care_pricing":{"$ref":"#/components/schemas/CarePricing"}},"required":["price","basis","currency"],"type":"object"},"HallPassRunMoney":{"type":"object","additionalProperties":false,"required":["amountCents","currency"],"properties":{"amountCents":{"type":"integer"},"currency":{"type":"string","pattern":"^[A-Z]{3}$"}}},"HallPassRunCustomerDisclosure":{"type":"object","additionalProperties":false,"required":["serviceFee","customerTotal"],"description":"What the customer was quoted when they approved: the service fee, its tax when one applies, and the customer total. Display only; `maxSpend` remains the run’s spending limit.","properties":{"serviceFee":{"$ref":"#/components/schemas/HallPassRunMoney"},"feeTax":{"$ref":"#/components/schemas/HallPassRunMoney","description":"Omitted when the service fee is untaxed."},"customerTotal":{"$ref":"#/components/schemas/HallPassRunMoney","description":"The service fee and its tax plus at most `maxSpend`."}}},"HallPassRunError":{"type":"object","required":["error","message"],"properties":{"error":{"const":true},"message":{"type":"string","description":"A sentence you can show staff as is."},"data":{"type":"null"}}},"HallPassRunInput":{"type":"object","additionalProperties":false,"required":["idempotencyKey"],"description":"What staff ask Hallpass to do. `kind` defaults to `general`; send exactly the details object for the kind. A run places the order by default, spending up to `maxSpend` or the staff cap; `rehearsal` stops at the checkout total and orders nothing. While the staff cap is 0 only rehearsals start. Family profile selection is a human-account-only alternative to manual attendee/guardian details; it requires rehearsal=true, maxSpend USD 0, autoAcceptDecisions=false, explicit execution route=cua boundary=checkout_review fallback=false, and no accounts, paymentCardId or answers. It creates no sealed snapshot or separate receipt.","properties":{"idempotencyKey":{"type":"string","format":"uuid","description":"One key per run for this staff member. A resend with the same details returns the run; different details answer 409."},"kind":{"type":"string","enum":["general","camp_booking"],"description":"Defaults to `general`."},"rehearsal":{"type":"boolean","default":false,"description":"Stop at the checkout total and order nothing. Off by default: a run places the order."},"maxSpend":{"oneOf":[{"$ref":"#/components/schemas/HallPassRunMoney"},{"type":"null"}],"description":"A USD spending limit up to HALLPASS_STAFF_MAX_SPEND_CENTS. Leave it out to spend up to that cap; send zero (or set `rehearsal`) for a rehearsal."},"autoAcceptDecisions":{"type":"boolean","description":"Let Hallpass accept routine questions within the limit. Totals, signatures and provider accounts always wait for staff."},"deadlineMinutes":{"type":"integer","minimum":10,"maximum":2880,"description":"Defaults to 120."},"general":{"type":"object","additionalProperties":false,"required":["instruction"],"properties":{"instruction":{"type":"string","minLength":1,"maxLength":4000},"startUrl":{"type":["string","null"],"description":"An https page to start from."},"subjects":{"type":"array","maxItems":10,"items":{"type":"object","additionalProperties":false,"required":["role","fields"],"properties":{"role":{"type":"string","pattern":"^[a-z][a-z_]{0,31}$","description":"Such as `traveler` or `recipient`."},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Keys from the Hallpass subject vocabulary (`full_name`, `email_address`, `phone_number`, `street_address`, `postal_code`, `departure_airport`, …), as `/openapi/hallpass.json` lists them. Every field sent is shared with the provider."}}}},"answers":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["key","label","value"],"properties":{"key":{"type":"string"},"label":{"type":"string"},"value":{"type":["string","boolean","number"]}}}},"references":{"type":"array","maxItems":20,"items":{"type":"object","additionalProperties":false,"required":["kind","label","value"],"properties":{"kind":{"type":"string","enum":["url","note","catalog"]},"label":{"type":"string"},"value":{"type":"string"}}}}}},"campBooking":{"oneOf":[{"type":"object","additionalProperties":false,"required":["sessionId","attendee","guardian"],"properties":{"sessionId":{"type":"string","format":"uuid","description":"A catalog session; Hallpass gets its organization, dates, price and registration link."},"attendee":{"type":"object","additionalProperties":false,"required":["firstName","lastName"],"properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"birthDate":{"type":"string","format":"date"},"grade":{"type":"string"},"allergies":{"type":"string"}}},"guardian":{"type":"object","additionalProperties":false,"required":["name","email"],"properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"givenName":{"type":"string","minLength":1,"maxLength":128,"description":"Optional explicitly provided guardian name part. Never inferred from the full name."},"familyName":{"type":"string","minLength":1,"maxLength":128,"description":"Optional explicitly provided guardian name part. Never inferred from the full name."}}},"answers":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["key","label","value"],"properties":{"key":{"type":"string"},"label":{"type":"string"},"value":{"type":["string","boolean","number"]}}}},"allowWaitlist":{"type":"boolean"}}},{"$ref":"#/components/schemas/HallPassCampBookingProfileInput"}]},"accounts":{"type":"array","maxItems":5,"description":"Site accounts for Pilgrim to sign in with, at most 5. Refused while HALLPASS_SECRETS_KEY is unset, and through Overseer. The username and password are sealed (AES-256-GCM) the moment they are recorded, never stored or shown in the clear, handed to OS Pilgrim as session account entries for their site only, and erased when the run ends or reaches its deadline. A run that attaches accounts runs only in Automatic mode, and a request whose text repeats a username or password is refused.","items":{"type":"object","additionalProperties":false,"required":["site","username","password"],"properties":{"site":{"type":"string","format":"uri","maxLength":2048,"description":"The https page the account signs in on, such as https://www.doordash.com; no port or credentials; any query or fragment is dropped. The account covers that site's registered domain."},"label":{"type":["string","null"],"maxLength":80,"description":"How staff name the account, such as DoorDash."},"username":{"type":"string","minLength":1,"maxLength":256,"description":"The username or email, trimmed. Shown afterwards only masked."},"password":{"type":"string","minLength":1,"maxLength":1024,"writeOnly":true,"description":"Kept exactly as typed. Never returned."}}}},"execution":{"$ref":"#/components/schemas/HallPassExecutionPolicy","description":"Optional explicit route and stop boundary. Omitted on legacy runs; selected execution mode remains a separate fact."},"paymentCardId":{"type":"string","description":"A saved card's `id` to pay with instead of the Hallpass platform card. Only for a run that may spend, routed to Pilgrim, when saved cards are enabled; left out, the platform card pays."}}},"HallPassRun":{"type":"object","additionalProperties":false,"required":["id","kind","summary","rehearsal","maxSpend","autoAcceptDecisions","requesterUserId","request","state","refusal","mode","stage","hold","decision","decisions","receipt","failure","uncertainty","reconciliation","accounts","replay","notices","pollAfterMs","deadlineAt","createdAt","updatedAt"],"description":"A staff run as staff see it. `state` is `submitting` until Hallpass acknowledges the task (each read resends it), `refused` if Hallpass turned it down, and otherwise Hallpass’s task state.","properties":{"id":{"type":"string","format":"uuid","description":"Also the Hallpass request ID."},"kind":{"type":"string","enum":["general","camp_booking"],"x-open-enum":true},"summary":{"type":"string"},"rehearsal":{"type":"boolean","description":"True when the run may not pay."},"maxSpend":{"$ref":"#/components/schemas/HallPassRunMoney"},"customerDisclosure":{"$ref":"#/components/schemas/HallPassRunCustomerDisclosure","description":"Present only when the run’s Hallpass task carries the customer’s disclosure, as a family booking with a service fee does. Runs started by staff carry none."},"autoAcceptDecisions":{"type":"boolean"},"requesterUserId":{"type":["string","null"],"format":"uuid"},"request":{"type":"object","additionalProperties":false,"required":["instruction","startUrl","sessionId","merchant"],"properties":{"instruction":{"type":["string","null"]},"startUrl":{"type":["string","null"]},"sessionId":{"type":["string","null"],"format":"uuid"},"merchant":{"type":["string","null"],"description":"The camp, for a camp booking run."}}},"state":{"type":"string","enum":["submitting","refused","accepted","running","awaiting_decision","confirmed","waitlisted","failed","uncertain","cancelled"],"x-open-enum":true},"refusal":{"type":["string","null"]},"mode":{"type":["string","null"],"enum":["fast","automatic",null],"x-open-enum":true},"stage":{"type":["string","null"],"enum":["locating","filling","reviewing_total","paying","confirming",null],"x-open-enum":true},"hold":{"description":"Why a running run is waiting on something nobody can answer: Pilgrim holding an earlier task for a person's review, Pilgrim's assistance switched off at its Mac, or Hallpass retrying its worker. `null` while the run moves on its own.","oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["code","message","since"],"properties":{"code":{"type":"string","enum":["pilgrim_queue_held","pilgrim_assistance_off","executor_retrying","pilgrim_busy"],"x-open-enum":true},"message":{"type":"string","minLength":1,"maxLength":500,"description":"What the run waits for, written for the requester."},"since":{"type":"string","format":"date-time"}}}]},"decision":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["decisionId","code","label","message","options","proposedOptionId","expiresAt","screenshot","secret"],"properties":{"decisionId":{"type":"string","format":"uuid"},"code":{"type":"string","enum":["unknown_question","choose_option","confirm_total","signature_required","provider_account_required","session_changed","unverified_step","answer_required","consent_required","registration_workflow_required"],"x-open-enum":true},"label":{"type":"string","description":"The decision's name as staff read it: the run page's heading and every notice's subject. Clients show it rather than wording the code themselves."},"message":{"type":"string"},"options":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["optionId","label","total"],"properties":{"optionId":{"type":"string"},"label":{"type":"string"},"total":{"oneOf":[{"$ref":"#/components/schemas/HallPassRunMoney"},{"type":"null"}]}}}},"proposedOptionId":{"type":["string","null"]},"expiresAt":{"type":"string","format":"date-time"},"screenshot":{"description":"The provider’s page as Hallpass last saw it, for an `unverified_step` decision; null otherwise.","oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["mediaType","data","width","height","capturedAt"],"properties":{"mediaType":{"type":"string","enum":["image/jpeg","image/png"]},"data":{"type":"string","description":"Base64 of the image."},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1},"capturedAt":{"type":"string","format":"date-time"}}}]},"secret":{"type":"boolean","description":"True for password, security-code, card-number and verification-code questions. Password answers are sealed and consumed before dispatch. Security-code answers accept three or four ASCII digits and travel through broker memory only; no code or ciphertext is persisted. Lost or uncertain delivery requires a new question. Card-number and verification-code answers cannot be collected or stored. Slack refuses secret answers."},"answerUnavailable":{"type":"string","maxLength":160,"description":"When present, the server-owned reason this answer cannot be collected. Show the reason without rendering an answer input or submit action."}}}]},"decisions":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["decisionId","code","label","outcome","optionId","total","decidedBy","decidedAt"],"properties":{"decisionId":{"type":"string","format":"uuid"},"code":{"type":"string"},"label":{"type":"string","description":"The decision's name as staff read it: the run page's heading and every notice's subject. Clients show it rather than wording the code themselves."},"outcome":{"type":"string","enum":["accepted","declined","expired"],"x-open-enum":true},"optionId":{"type":["string","null"]},"total":{"oneOf":[{"$ref":"#/components/schemas/HallPassRunMoney"},{"type":"null"}],"description":"The total staff confirmed, for a confirm_total decision."},"decidedBy":{"type":["string","null"],"enum":["requester","auto",null],"x-open-enum":true},"decidedAt":{"type":"string","format":"date-time"}}}},"receipt":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["providerReference","providerStatus","merchant","paymentStatus","amount","amountDue","lineItems","evidence","confirmedAt","bookedSession","fulfillment"],"properties":{"providerReference":{"type":"string"},"providerStatus":{"type":"string"},"merchant":{"type":"string"},"paymentStatus":{"type":"string","enum":["charged","not_required","deferred"],"x-open-enum":true},"paymentCardReference":{"type":"string","description":"The opaque reference of the platform card that paid, when Hallpass names one. Never a card number."},"amount":{"oneOf":[{"$ref":"#/components/schemas/HallPassRunMoney"},{"type":"null"}]},"amountDue":{"oneOf":[{"$ref":"#/components/schemas/HallPassRunMoney"},{"type":"null"}]},"lineItems":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["kind","label","amount"],"properties":{"kind":{"type":"string"},"label":{"type":"string"},"amount":{"$ref":"#/components/schemas/HallPassRunMoney"}}}},"evidence":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["kind","reference","summary","capturedAt"],"properties":{"kind":{"type":"string"},"reference":{"type":"string","description":"Where the evidence is: `pilgrim:<task id>` for Pilgrim’s record of its task, `hallpass:<task id>:checkpoint` for Hallpass’s last step, or what a person cited."},"summary":{"type":"string"},"capturedAt":{"type":"string","format":"date-time"}}}},"confirmedAt":{"type":"string","format":"date-time"},"bookedSession":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["sessionId","label","startDate","endDate"],"properties":{"sessionId":{"type":"string","format":"uuid"},"label":{"type":"string"},"startDate":{"type":["string","null"],"format":"date"},"endDate":{"type":["string","null"],"format":"date"}}}]},"fulfillment":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["summary","startsAt","endsAt"],"properties":{"summary":{"type":"string"},"startsAt":{"type":["string","null"],"format":"date-time"},"endsAt":{"type":["string","null"],"format":"date-time"}}}]},"inspection":{"type":"object","additionalProperties":false,"required":["version","url","sha256"],"properties":{"version":{"type":"string","enum":["public-inspection-v1"]},"url":{"type":"string"},"sha256":{"type":"string"}}}}}]},"failure":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["code","message","retryable","providerTotal"],"properties":{"code":{"type":"string","description":"A Hallpass failure code; `stopped_before_payment` ends a rehearsal at the total in `providerTotal`."},"message":{"type":"string"},"retryable":{"type":"boolean"},"providerTotal":{"oneOf":[{"$ref":"#/components/schemas/HallPassRunMoney"},{"type":"null"}]}}}]},"uncertainty":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["suspected","lastStage","evidence","message"],"properties":{"suspected":{"type":"array","items":{"type":"string"}},"lastStage":{"type":"string"},"evidence":{"type":"array","description":"What the run held when it stopped, for whoever reconciles it: `checkpoint` (Hallpass's last recorded step) always, and `provider_task` (the provider's record of the task: its state, failure code and account of the stop and of any order it reported) when Hallpass read one at the stop, and again once the provider's task is finished or held, when Hallpass's sweep captures its final account before releasing it.","items":{"type":"object","additionalProperties":false,"required":["kind","reference","summary","capturedAt"],"properties":{"kind":{"type":"string"},"reference":{"type":"string","description":"Where the evidence is: `pilgrim:<task id>` for Pilgrim’s record of its task, `hallpass:<task id>:checkpoint` for Hallpass’s last step, or what a person cited."},"summary":{"type":"string"},"capturedAt":{"type":"string","format":"date-time"}}}},"message":{"type":"string"}}}]},"reconciliation":{"description":"How staff reconciled a run that ended `uncertain`: who attested, what they said and the evidence they relied on. `null` unless a person reconciled the run, which is then `confirmed` or `failed`.","oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["reconciliationId","reconciledBy","statement","evidence","cardAttested","reconciledAt"],"properties":{"reconciliationId":{"type":"string","format":"uuid"},"reconciledBy":{"type":"string","format":"uuid","description":"The signed-in staff account that attested."},"statement":{"type":"string"},"evidence":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["kind","reference","summary"],"properties":{"kind":{"type":"string","enum":["confirmation_page","confirmation_email","provider_account_listing","payment_ledger"]},"reference":{"type":"string","minLength":1,"maxLength":500,"description":"Where the evidence is, such as `pilgrim:<task id>`, the merchant’s order number or the platform-card ledger entry."},"summary":{"type":"string","minLength":1,"maxLength":1000,"description":"What it shows, in words."}}}},"cardAttested":{"type":"boolean","description":"True only on runs reconciled before 2026-10-27 whose card staff attested because the payment step predated the card pin. The attestation is retired; new reconciliations are false."},"reconciledAt":{"type":"string","format":"date-time"}}}]},"accounts":{"type":"array","description":"The run's attached site accounts by site, label and masked username, whether each is held for its executor, delivered to it, or erased, and whether the requester attached it or Hallpass created it at the provider for the family. Never a password.","items":{"type":"object","additionalProperties":false,"required":["site","label","username","state","origin","createdAtProvider"],"properties":{"site":{"type":"string","format":"uri"},"label":{"type":["string","null"]},"username":{"type":"string","description":"Masked, such as r…@example.com."},"state":{"type":"string","enum":["held","delivered","erased"],"x-open-enum":true},"origin":{"type":"string","enum":["attached","created"],"x-open-enum":true,"description":"created: Hallpass mints this account's password at approval and creates the account at the provider during the run."},"createdAtProvider":{"type":["string","null"],"format":"date-time","description":"When the run created the account at the provider; null until then and for an attached account."}}}},"replay":{"$ref":"#/components/schemas/HallPassRunReplay"},"notices":{"type":"array","description":"How the run's decisions reached its requester away from the run page, and when the page showed them, oldest first (DEV-1299).","items":{"$ref":"#/components/schemas/HallPassRunNotice"}},"pollAfterMs":{"type":["integer","null"],"description":"How long to wait before reading the run again; null once nothing will change."},"deadlineAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"execution":{"$ref":"#/components/schemas/HallPassExecutionPolicy","description":"Optional explicit route and stop boundary. Omitted on legacy runs; selected execution mode remains a separate fact."},"paymentRecord":{"$ref":"#/components/schemas/HallPassRunPaymentRecord"}}},"HallPassPlan":{"type":"object","additionalProperties":false,"required":["kind","reason","sessions","missing"],"description":"How Hallpass would read a request before anyone approves it. A catalog session is a camp booking; a page on a listed organization's site offers its current sessions while the request stays general; anything else is general.","properties":{"kind":{"type":"string","enum":["general","camp_booking"],"x-open-enum":true},"reason":{"type":"string","description":"A sentence you can show staff as is."},"sessions":{"type":"array","maxItems":10,"items":{"type":"object","additionalProperties":false,"required":["sessionId","sessionName","organizationName","startDate","endDate"],"properties":{"sessionId":{"type":"string","format":"uuid"},"sessionName":{"type":["string","null"]},"organizationName":{"type":"string"},"startDate":{"type":["string","null"],"format":"date"},"endDate":{"type":["string","null"],"format":"date"}}}},"missing":{"type":"array","items":{"type":"string"},"description":"What the typed variant still needs, as sentences."}}},"FamilyPlanActivity":{"type":"object","required":["id","name","imageUrl"],"additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"description":"A catalog activity as a calendar entry shows it: its name and first photo (its cover, else its first gallery photo; null without one)."},"FamilyPlanOrganization":{"type":"object","required":["id","name","logoUrl"],"additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The camp's selected logo; null when it has none. Once rendered it is the logo's public, cross-origin rendition (GET /api/media/logos/{file}), else its source URL."}},"description":"A camp as an idea or a registration alert shows it: its name and selected logo (null without one)."},"FamilyPlanProviderTask":{"type":"object","required":["label","url"],"additionalProperties":false,"properties":{"label":{"type":"string"},"url":{"type":"string"}},"description":"Something the parent must still do on the provider's site, and where."},"FamilyPlanIdea":{"type":"object","required":["activityId","sessionId","title","organization","price","startTime","endTime","friends"],"additionalProperties":false,"properties":{"activityId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"sessionId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"title":{"type":"string"},"organization":{"anyOf":[{"$ref":"#/components/schemas/FamilyPlanOrganization"},{"type":"null"}]},"price":{"anyOf":[{"$ref":"#/components/schemas/BookingMoney"},{"type":"null"}],"description":"The session's price when it states one quote for the whole session, as a booking's listed price reads it; null otherwise."},"startTime":{"anyOf":[{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},{"type":"null"}],"description":"The session's daily start time when every day keeps the same hours; null otherwise."},"endTime":{"anyOf":[{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},{"type":"null"}]},"friends":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanCalendarFriend"},"description":"Friends who share their plans and have a booking or registered plan for the idea's session."}},"description":"An option for a day nothing covers yet: a saved option, a followed session or a catalog suggestion for the child, with what a screen books from. The catalog parts (organization, price, times) are null for a record no longer in the catalog. At most a few per day or group (policy), one per session."},"FamilyPlanHomeFriend":{"type":"object","required":["userId","publicName","publicNameKnown","photoUrl"],"additionalProperties":false,"properties":{"userId":{"type":"string","format":"uuid"},"publicName":{"type":["string","null"]},"publicNameKnown":{"type":"boolean","description":"False when the parent has no name to show; publicName is then null and the client writes its own wording."},"photoUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}}},"FamilyBookingPolicy":{"type":"object","description":"The booking money policy: School’s Out’s service fee per child, which every new booking carries (one booking is one child), and the group rate, which refunds one child’s fee to each family’s wallet once two or more families’ bookings for a session are confirmed.","required":["serviceFee","groupRate"],"additionalProperties":false,"properties":{"serviceFee":{"type":"object","required":["perChild"],"additionalProperties":false,"properties":{"perChild":{"$ref":"#/components/schemas/BookingMoney","description":"Charged per child, per booking, when it is booked or applied."}}},"groupRate":{"oneOf":[{"type":"object","required":["perFamily","families"],"additionalProperties":false,"properties":{"perFamily":{"$ref":"#/components/schemas/BookingMoney","description":"Credited once per family and session, whatever its number of children: one child’s service fee."},"families":{"type":"integer","minimum":2,"description":"How many families’ confirmed bookings for one session earn it."}}},{"type":"null"}],"description":"Null when there is no service fee to refund."}}},"SessionScheduleDateRange":{"additionalProperties":false,"description":"An enrollment's stated local calendar bounds, with no attended weekday implied. Either end may be unknown; a start with no end is ongoing. end_date must not precede start_date.","properties":{"end_date":{"description":"Inclusive last local date, YYYY-MM-DD, or omitted when unstated (ongoing).","format":"date","type":"string"},"start_date":{"description":"Inclusive first local date, YYYY-MM-DD, or omitted when unstated.","format":"date","type":"string"}},"type":"object"},"FamilyNotificationEmailChannel":{"type":"object","additionalProperties":false,"properties":{"email":{"type":"boolean"}},"required":["email"],"description":"Whether the row is sent by email; it has no push or SMS."},"FamilyNotificationQuietHours":{"type":"object","additionalProperties":false,"properties":{"on":{"type":"boolean"},"from":{"type":"integer","minimum":0,"maximum":23,"description":"The hour quiet hours start, on a 24-hour clock in the household's time zone; quiet hours never hold a household without one."},"to":{"type":"integer","minimum":0,"maximum":23,"description":"The hour they end; never equal to `from`, and earlier than it when the window wraps past midnight."}},"required":["on","from","to"],"description":"While on, email, push and SMS wait until the window ends; the inbox entry is stored at once. Registration reminders (registration or spots opening at a followed camp) come through them."},"FamilyBookingQuoteInput":{"type":"object","additionalProperties":false,"required":["sessionId","childProfileIds"],"description":"What booking a session for some children would cost, with the payment choices an approval takes.","properties":{"sessionId":{"type":"string","format":"uuid"},"childProfileIds":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"type":"string","format":"uuid"},"description":"The children in checkout order. Credits are applied to them in this order."},"maxSpend":{"$ref":"#/components/schemas/BookingMoney","description":"The spending limit each approval will send, validated as an approval's (positive, at most 2500000 cents, the listed price's currency when the session lists one). Omit to quote the session's listed price; without either, every child's `money` is null."},"useCreditsCents":{"type":"integer","minimum":1,"description":"Wallet credits to spend across the children, in USD cents. More than the wallet holds is capped at the balance. Omit to spend none."},"paymentMethod":{"type":"string","enum":["default","backup"],"description":"The saved card the approvals will hold. Defaults to `default`."}}},"FamilyBookingQuoteChild":{"type":"object","additionalProperties":false,"required":["childProfileId","childProfileVersion","eligibility","wouldWaitlist","money"],"description":"One child's booking as an approval would make it.","properties":{"childProfileId":{"type":"string","format":"uuid"},"childProfileVersion":{"type":"integer","minimum":1,"description":"The profile version to send as the approval's `childProfileVersion`."},"eligibility":{"type":"object","additionalProperties":false,"required":["alreadyBooked","reuseConsentMissing","missingFields"],"properties":{"alreadyBooked":{"type":"boolean","description":"The child already has a booking in progress or confirmed for this session; an approval is refused (409). Such a child has no `money` and takes no credits."},"reuseConsentMissing":{"type":"boolean","description":"The profile does not allow reuse yet. The approval must send `reuseAllowed: true` (only the profile's owner may), or it is refused (409)."},"missingFields":{"type":"array","items":{"$ref":"#/components/schemas/BookingAlwaysSharedField"},"description":"Fields of the session's booking form (`alwaysShared`) the booking would send empty: the child's from their profile, the guardian's from the account of the adult quoting (the owner or a co-parent), exactly as the approval reads them. Save the child's with the approval's `profileFields` and the guardian's on the account, or the approval is refused (400)."}}},"wouldWaitlist":{"type":["boolean","null"],"description":"Whether this child would take a waitlist spot, counting the places the children before it take: true when the session is waitlisting or its places run out, false when an open session still has a place. Null when the catalog cannot tell (no capacity listed); Hallpass decides then. A waitlist spot is never charged; its hold is released."},"money":{"oneOf":[{"type":"object","additionalProperties":false,"required":["maxSpend","serviceFee","creditsApplied","cardHold","stripeMinimumApplied"],"properties":{"maxSpend":{"$ref":"#/components/schemas/BookingMoney","description":"The spending limit: `maxSpend` as sent, else the session's listed price."},"serviceFee":{"$ref":"#/components/schemas/BookingMoney","description":"The service fee the booking carries, charged when it is booked or applied."},"creditsApplied":{"$ref":"#/components/schemas/BookingMoney","description":"The credits to send as this approval's `useCreditsCents` (omit it when zero)."},"cardHold":{"$ref":"#/components/schemas/BookingMoney","description":"What the approval holds on the card: the limit plus the fee, less the credits. Equals the booking's `payment.cardHoldCents`."},"stripeMinimumApplied":{"type":"boolean","description":"The card part left after credits is below Stripe's minimum, so the hold is raised to it; capture still charges only what is owed."}}},{"type":"null"}],"description":"Null when the child is already booked, or when no `maxSpend` was sent and the session has no single listed session price."}}},"FamilyBookingQuote":{"type":"object","additionalProperties":false,"required":["sessionId","children","credits","paymentMethod","totals"],"description":"Checkout totals from the API, built from the same functions approval and settlement use. Each child is one booking; send one approval per child with its `maxSpend`, `childProfileVersion` and `creditsApplied`. Reading a quote books and holds nothing.","properties":{"sessionId":{"type":"string","format":"uuid"},"children":{"type":"array","items":{"$ref":"#/components/schemas/FamilyBookingQuoteChild"},"description":"In the order asked."},"credits":{"type":"object","additionalProperties":false,"required":["available","requested","applied"],"properties":{"available":{"$ref":"#/components/schemas/BookingMoney","description":"The wallet balance."},"requested":{"oneOf":[{"$ref":"#/components/schemas/BookingMoney"},{"type":"null"}],"description":"`useCreditsCents` as sent; null without it."},"applied":{"$ref":"#/components/schemas/BookingMoney","description":"Credits applied across the children, first child first, each at most its limit plus fee. None for a session not priced in USD."}}},"paymentMethod":{"type":"object","additionalProperties":false,"required":["choice","saved"],"properties":{"choice":{"type":"string","enum":["default","backup"]},"saved":{"type":"boolean","description":"The account has that saved card. Without it, an approval that needs a hold fails with `payment_method_required`."}}},"totals":{"type":"object","additionalProperties":false,"required":["maxSpend","serviceFees","credits","cardHolds"],"description":"Sums over the children with `money`.","properties":{"maxSpend":{"$ref":"#/components/schemas/BookingMoney"},"serviceFees":{"$ref":"#/components/schemas/BookingMoney"},"credits":{"$ref":"#/components/schemas/BookingMoney"},"cardHolds":{"$ref":"#/components/schemas/BookingMoney"}}}}},"FamilyMemberRole":{"type":"string","enum":["co_parent","caregiver"],"description":"A member's role. co_parent: books (the owner's card pays), sees payments and credits, edits children and plans. caregiver: sees the granted children's care view, schedules and plans; no money, no edits."},"FamilyRole":{"type":"string","enum":["owner","co_parent","caregiver"],"description":"The viewer's role in the family they act in. The owner owns the children, bookings, wallet and cards, and pays."},"FamilyAdult":{"type":"object","required":["userId","name","email","you"],"properties":{"userId":{"type":"string","format":"uuid"},"name":{"type":["string","null"],"description":"The account's full name; null when it has none."},"email":{"type":["string","null"],"format":"email","description":"Null for a caregiver viewer, who sees the family's adults by name only."},"you":{"type":"boolean"}}},"FamilyMember":{"allOf":[{"$ref":"#/components/schemas/FamilyAdult"},{"type":"object","required":["role","relation","allChildren","childIds","joinedAt"],"properties":{"role":{"$ref":"#/components/schemas/FamilyMemberRole"},"relation":{"type":["string","null"],"description":"How the member relates to the family, as the owner wrote it (Grandparent)."},"allChildren":{"type":"boolean","description":"The member sees every child, including children added later."},"childIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"The children the member sees, oldest first."},"joinedAt":{"type":"string","format":"date-time"}}}]},"FamilyInvitationStatus":{"type":"string","enum":["pending","accepted","declined","canceled","expired"],"description":"Derived from the row: canceled by the owner, expired after expiresAt (also when a newer invitation to the address replaced an expired one)."},"FamilyInvitation":{"type":"object","required":["id","email","name","role","relation","allChildren","childIds","status","createdAt","sentAt","expiresAt","declinedAt","newInviteRequestedAt"],"properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"name":{"type":["string","null"],"description":"The invitee's name as the owner wrote it."},"role":{"$ref":"#/components/schemas/FamilyMemberRole"},"relation":{"type":["string","null"]},"allChildren":{"type":"boolean"},"childIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"The invited children still in the family; every child of the family when allChildren."},"status":{"$ref":"#/components/schemas/FamilyInvitationStatus"},"createdAt":{"type":"string","format":"date-time"},"sentAt":{"type":"string","format":"date-time","description":"The latest send; a resend or copied link moves it and expiresAt."},"expiresAt":{"type":"string","format":"date-time"},"declinedAt":{"type":["string","null"],"format":"date-time"},"newInviteRequestedAt":{"type":["string","null"],"format":"date-time","description":"When the invitee asked for a new invitation."}}},"FamilyMembers":{"type":"object","required":["owner","members","invitations","viewerRole"],"properties":{"owner":{"$ref":"#/components/schemas/FamilyAdult"},"members":{"type":"array","items":{"$ref":"#/components/schemas/FamilyMember"},"description":"By join time."},"invitations":{"type":"array","items":{"$ref":"#/components/schemas/FamilyInvitation"},"description":"Open invitations (pending or expired) and those declined in the last seven days, newest first. Empty for a caregiver viewer."},"viewerRole":{"$ref":"#/components/schemas/FamilyRole"}}},"FamilyInvitationPreview":{"type":"object","required":["status","inviterName","householdName","role","relation","name","email","childFirstNames","adults","children","expiresAt","newInviteRequestedAt"],"properties":{"status":{"$ref":"#/components/schemas/FamilyInvitationStatus"},"inviterName":{"type":"string","description":"The owner's full name, else the local part of their email."},"householdName":{"type":"string","description":"\"Okafor family\" from the owner's surname, else \"jordan’s family\"."},"role":{"$ref":"#/components/schemas/FamilyMemberRole"},"relation":{"type":["string","null"],"description":"Null unless pending."},"name":{"type":["string","null"],"description":"The invitee's name as the owner wrote it; null unless pending."},"email":{"type":["string","null"],"description":"The invited address masked (r***@example.com); null unless pending."},"childFirstNames":{"type":"array","items":{"type":"string"},"description":"The invited children's first names; empty unless pending."},"adults":{"type":"integer","minimum":0,"description":"The family's adults (owner and members); 0 unless pending."},"children":{"type":"integer","minimum":0,"description":"The family's children; 0 unless pending."},"expiresAt":{"type":"string","format":"date-time"},"newInviteRequestedAt":{"type":["string","null"],"format":"date-time"}}},"FamilyBookingSchedule":{"type":"object","additionalProperties":false,"required":["id","phase","status","childProfileId","organization","activity","session","progressStage","hold","pollAfterMs","waitlist","canceledBy","note","replacesBookingId","replacedByBookingId","bookedBy","createdAt","updatedAt"],"description":"A booking as a caregiver (a role without read_money) sees it: the schedule, with no money, approval, decisions, payment, refund or events.","properties":{"id":{"type":"string","format":"uuid"},"phase":{"$ref":"#/components/schemas/BookingPhase"},"status":{"$ref":"#/components/schemas/BookingStatus"},"childProfileId":{"type":["string","null"],"format":"uuid","description":"`null` after the profile is deleted. The booking itself remains."},"organization":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"activity":{"anyOf":[{"type":"object","required":["id","name"],"additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}}},{"type":"null"}],"description":"The booked session's catalog activity, as it was at approval like organization and session; null when the session had none. A change of session stays within it."},"session":{"type":"object","required":["id","name","startDate","endDate","startTime","endTime","dates","dayTimes"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"startDate":{"type":["string","null"],"format":"date","description":"First day of the session."},"endDate":{"type":["string","null"],"format":"date","description":"Last day of the session."},"startTime":{"type":["string","null"],"pattern":"^[0-9]{2}:[0-9]{2}$","description":"Daily start time (HH:MM) when the session has one; snapshotted at approval."},"endTime":{"type":["string","null"],"pattern":"^[0-9]{2}:[0-9]{2}$","description":"Daily end time (HH:MM) when the session has one."},"dates":{"type":"array","maxItems":60,"items":{"type":"string","format":"date"},"description":"Published attendance dates, at most 60; empty when the camp published only a date range."},"dayTimes":{"type":"array","maxItems":60,"items":{"$ref":"#/components/schemas/FamilyPlanDayTime"},"description":"The days whose own times replace startTime and endTime, by date, snapshotted at approval: a day with different hours, or an overnight stay's drop-off (first day, startTime only) and pick-up (last day, endTime only) with neither between. Empty when every day keeps startTime and endTime."}}},"progressStage":{"type":["string","null"],"enum":["resolving_session","filling_form","reviewing_total","paying","confirming",null],"x-open-enum":true,"description":"The furthest checkout stage Hallpass reported, or `null` before it reports one. New stages may be added."},"hold":{"description":"Why a running run is waiting on something nobody can answer: Pilgrim holding an earlier task for a person's review, Pilgrim's assistance switched off at its Mac, or Hallpass retrying its worker. `null` while the run moves on its own.","oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["code","message","since"],"properties":{"code":{"type":"string","enum":["pilgrim_queue_held","pilgrim_assistance_off","executor_retrying"],"x-open-enum":true},"message":{"type":"string","minLength":1,"maxLength":500,"description":"What the run waits for, written for the requester."},"since":{"type":"string","format":"date-time"}}}]},"pollAfterMs":{"type":["integer","null"],"description":"How long to wait before reading the booking again. `null` once nothing will change without the parent."},"waitlist":{"oneOf":[{"type":"object","required":["position","updatedAt"],"properties":{"position":{"type":["integer","null"],"minimum":1,"description":"The place the camp reported, 1 is next; `null` when it gave none."},"updatedAt":{"type":"string","format":"date-time"}}},{"type":"null"}]},"canceledBy":{"type":["string","null"],"enum":["parent","provider","system",null],"description":"Who cancelled a cancelled booking."},"note":{"type":["string","null"],"maxLength":1000,"description":"What the family should know for this booking, such as what to bring, from the provider’s email. An undone email update puts back the note it replaced."},"replacesBookingId":{"type":["string","null"],"format":"uuid","description":"The booking this one changes to another session."},"replacedByBookingId":{"type":["string","null"],"format":"uuid","description":"The live booking changing this one. This booking stays until that one is booked."},"bookedBy":{"anyOf":[{"type":"object","required":["userId","name"],"properties":{"userId":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}},"additionalProperties":false},{"type":"null"}],"description":"The family member (co-parent) who approved the booking; null when the family's owner did. The booking, its card hold and its payments are the owner's."},"createdAt":{"type":"string","format":"date-time","description":"When the booking was created."},"updatedAt":{"type":"string","format":"date-time","description":"When the booking last changed."}}},"FamilyFeedInstructions":{"type":"object","additionalProperties":false,"required":["instructions","defaultInstructions"],"properties":{"instructions":{"type":["string","null"],"minLength":1,"maxLength":4000,"description":"The signed-in adult's own words for what their feed should carry; null while they read the default feed. Every adult in a family (owner, co-parent, caregiver) has their own: they shape only that adult's feed and its \"You asked for …\" items."},"defaultInstructions":{"type":"string","description":"Read-only. The feed's wording of what it carries for this adult while they have written none, from the family's area and the children the adult sees. Saving null returns the adult to it; saving this text is allowed and changes nothing the feed does."}}},"FamilyFeedInstructionsInput":{"type":"object","additionalProperties":false,"required":["instructions"],"properties":{"instructions":{"type":["string","null"],"minLength":1,"maxLength":4000,"description":"The adult's own words, trimmed; 1 to 4000 characters. null removes them and returns the adult to the default feed."}}},"FamilyAccountCalendarAdult":{"type":"object","additionalProperties":false,"required":["userId","name"],"description":"The family adult whose calendar an event is in.","properties":{"userId":{"type":"string","format":"uuid"},"name":{"type":["string","null"],"description":"The account's full name; null when it has none."}}},"CanonicalFilter":{"description":"A bounded recursive canonical predicate tree. Every node has exactly one form. Field names, operators, relations and special predicates must be advertised by the current entity table filterCapabilities. Every branch is validated against current tier and preview disclosure before execution. Legacy query parameters combine with this tree using AND. No general negation. Null is unknown, never false, free or unrestricted. Conditions inside one related.where match the same related record; separate related nodes may match different records. Runtime also validates field-specific types, permissions, depth, total node count and encoded byte size. Map, member and summary operations project the selected root through the camp cohort described by their filterKind parameter; global search suggestions remain separate.","oneOf":[{"$ref":"#/components/schemas/CanonicalFilterAll"},{"$ref":"#/components/schemas/CanonicalFilterAny"},{"$ref":"#/components/schemas/CanonicalFilterField"},{"$ref":"#/components/schemas/CanonicalFilterRelated"},{"$ref":"#/components/schemas/CanonicalFilterRadius"},{"$ref":"#/components/schemas/CanonicalFilterPrice"},{"$ref":"#/components/schemas/CanonicalFilterSchedule"}],"examples":[{"related":{"relation":"sessions","match":"any","where":{"all":[{"field":"age_range_years","op":"contains","value":10},{"price":{"maximum":400,"currency":"USD","basis":"week"}}]}}},{"any":[{"field":"overnight","op":"missing"},{"field":"overnight","op":"eq","value":false}]},{"schedule":{"mode":"covers","from":"2027-06-14","to":"2027-06-18","daysOfWeek":["monday","tuesday","wednesday","thursday","friday"],"startTime":"09:00","endTime":"17:00","timeZone":"venueLocal","includeCare":true}}]},"CanonicalFilterAll":{"type":"object","additionalProperties":false,"properties":{"all":{"type":"array","items":{"$ref":"#/components/schemas/CanonicalFilter"},"minItems":1,"maxItems":64}},"required":["all"]},"CanonicalFilterAny":{"type":"object","additionalProperties":false,"properties":{"any":{"type":"array","items":{"$ref":"#/components/schemas/CanonicalFilter"},"minItems":1,"maxItems":64}},"required":["any"]},"CanonicalFilterField":{"type":"object","additionalProperties":false,"properties":{"field":{"type":"string","minLength":1,"description":"Canonical field name from filterCapabilities.fields; age_range_months is a compatibility alias and is not accepted. Filter-only score (numeric, 0 through 100) and score_band (weak, fair, good, strong) read the current entity's persisted list-score source, including in related predicates. They require paid access and are unavailable in previews. Missing scores are null, not zero or an unavailable band; use missing or eq null. score supports numeric comparisons and in; score_band supports eq and in; both support missing and present. Filtering does not add scores to entity records or calculate scores during reads."},"op":{"type":"string","enum":["eq","in","missing","present","empty","lt","lte","gt","gte","containsText","matchesRegion","containsAny","containsAll","contains","overlaps"]},"value":{"description":"Typed JSON value for the selected field and operator. eq supports null (same as missing); JSON fields compare whole structural values. in requires 1–100 non-null scalar values. containsAny/containsAll require 1–100 whole members; categories and text arrays require strings. Non-array JSON never satisfies membership. contains takes one whole age/grade; overlaps takes two inclusive range endpoints with null for an open bound. Number and boolean fields require JSON numbers and booleans; dates use YYYY-MM-DD, timestamps RFC 3339. Text eq/in are exact and case-sensitive; containsText is a literal case-insensitive substring. matchesRegion accepts nonblank text on region only: US state/territory codes and full names are aliases, with case and surrounding ASCII whitespace ignored. Other labels use trimmed case-insensitive equality. This matches recorded labels without verifying country; eq/in retain literal semantics."}},"required":["field","op"],"description":"A field predicate. missing means database null only. present includes false, zero, blank text and empty collections. empty excludes null and matches empty or space-only text, an empty array or an empty JSON object. Operators depend on the field type; use filterCapabilities rather than assuming every listed operator applies.","oneOf":[{"properties":{"op":{"enum":["missing","present","empty"]}},"not":{"required":["value"]}},{"properties":{"op":{"enum":["eq"]}},"required":["value"]},{"properties":{"op":{"enum":["in"]},"value":{"type":"array","minItems":1,"maxItems":100,"items":{"type":["string","number","boolean"]}}},"required":["value"]},{"properties":{"op":{"enum":["containsText"]},"value":{"type":"string"}},"required":["value"]},{"properties":{"field":{"enum":["region"]},"op":{"enum":["matchesRegion"]},"value":{"type":"string","minLength":1,"pattern":"\\S"}},"required":["value"]},{"properties":{"op":{"enum":["lt","lte","gt","gte"]},"value":{"type":["number","string"]}},"required":["value"]},{"properties":{"op":{"enum":["containsAny","containsAll"]},"value":{"type":"array","minItems":1,"maxItems":100,"items":{}}},"required":["value"]},{"properties":{"op":{"enum":["contains"]},"value":{"type":"integer"}},"required":["value"]},{"properties":{"op":{"enum":["overlaps"]},"value":{"type":"array","minItems":2,"maxItems":2,"items":{"type":["integer","null"]}}},"required":["value"]}]},"CanonicalFilterRelated":{"type":"object","additionalProperties":false,"properties":{"related":{"type":"object","additionalProperties":false,"properties":{"relation":{"type":"string","enum":["organization","activity","activities","sessions"],"description":"Organizations expose activities/sessions; activities expose organization/sessions; sessions expose organization/activity. Use the current capability map. A session direct organization wins; its activity supplies the organization only when direct ownership is absent. Organization descendant chains retain that owner."},"match":{"type":"string","enum":["any","none"],"description":"any requires a matching related record; none requires no matching related record in the disclosed population."},"where":{"$ref":"#/components/schemas/CanonicalFilter"}},"required":["relation","match","where"]}},"required":["related"]},"CanonicalFilterRadius":{"type":"object","additionalProperties":false,"properties":{"radius":{"type":"object","additionalProperties":false,"properties":{"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180},"radiusMeters":{"type":"number","exclusiveMinimum":0}},"required":["latitude","longitude","radiusMeters"]}},"required":["radius"],"description":"Strict distance predicate using the selected entity own valid, disclosed coordinates with coordinate_precision exact, building, parcel or street. Includes points on the radius boundary. Missing, masked, approximate and unknown-precision locations do not match. No parent location fallback or possible-area match is used."},"CanonicalFilterPrice":{"type":"object","additionalProperties":false,"properties":{"price":{"type":"object","additionalProperties":false,"properties":{"minimum":{"type":"number","minimum":0},"maximum":{"type":"number","minimum":0},"currency":{"type":"string","pattern":"^[A-Z]{3}$"},"basis":{"type":"string","enum":["hour","day","week","month","session"]}},"required":["currency","basis"],"anyOf":[{"required":["minimum"]},{"required":["maximum"]}]}},"required":["price"],"description":"Session scope only. Match one recorded tuition quote with the requested currency and basis and at least one inclusive finite nonnegative bound. minimum must not exceed maximum. Missing amount, currency or basis cannot match; zero can. No currency conversion, rate conversion, attendance multiplication, fees, care amounts or comparable total is inferred. Separate price predicates can match different quotes."},"CanonicalFilterSchedule":{"type":"object","additionalProperties":false,"properties":{"schedule":{"type":"object","additionalProperties":false,"properties":{"mode":{"type":"string","enum":["overlap","covers"],"description":"With explicit dates or a from/to range, overlap matches at least one requested attendance date and covers matches every requested attendance date. A time point must fall within known daily hours; a time window must overlap in overlap mode or be fully covered by one source window in covers mode. Without date limits, either mode matches any qualifying configuration and any requested weekday."},"dates":{"type":"array","items":{"type":"string","format":"date"},"uniqueItems":true,"minItems":1,"maxItems":366},"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"daysOfWeek":{"type":"array","items":{"type":"string","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]},"uniqueItems":true,"minItems":1,"maxItems":7,"description":"Lowercase weekdays, allowed with from/to or without date limits; disallowed with explicit dates. Without date limits, any requested weekday must have a real, non-excluded occurrence in the same configuration as any requested hours."},"atTime":{"type":"string","pattern":"^(?:[01][0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?$","description":"A local time point in HH:mm or HH:mm:ss, exclusive with startTime/endTime. Opening is inclusive and closing is exclusive."},"startTime":{"type":"string","pattern":"^(?:[01][0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?$"},"endTime":{"type":"string","pattern":"^(?:(?:[01][0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?|24:00(?::00)?)$"},"timeZone":{"type":"string","default":"venueLocal","minLength":1,"description":"venueLocal or a valid IANA time zone. venueLocal compares stored civil dates/clocks and does not require a known source zone. IANA requests require explicit dates or from/to plus atTime or a startTime/endTime window, compare instants, and require the session own known IANA zone. Ambiguous or nonexistent request endpoints are rejected; ambiguous/nonexistent source endpoints do not match. Local is not accepted."},"includeCare":{"type":"boolean","default":false,"description":"Point or window requests only. Extend daily hours with explicitly available care and known care clocks, subject to care-hour access (Starter or higher). Missing care facts never extend a window. Invalid optional care endpoints fall back to valid core hours; care cannot rescue an invalid core window."}},"required":["mode"],"oneOf":[{"required":["dates"],"not":{"anyOf":[{"required":["from"]},{"required":["to"]},{"required":["daysOfWeek"]}]}},{"required":["from","to"],"not":{"required":["dates"]}},{"not":{"anyOf":[{"required":["dates"]},{"required":["from"]},{"required":["to"]}]},"anyOf":[{"required":["daysOfWeek"]},{"required":["atTime"]},{"required":["startTime"]}]}],"dependentRequired":{"from":["to"],"to":["from"],"startTime":["endTime"],"endTime":["startTime"]},"allOf":[{"if":{"required":["atTime"]},"then":{"not":{"anyOf":[{"required":["startTime"]},{"required":["endTime"]}]}}},{"if":{"required":["includeCare"],"properties":{"includeCare":{"const":true}}},"then":{"anyOf":[{"required":["atTime"]},{"required":["startTime","endTime"]}]}},{"if":{"required":["timeZone"],"properties":{"timeZone":{"not":{"const":"venueLocal"}}}},"then":{"allOf":[{"anyOf":[{"required":["atTime"]},{"required":["startTime","endTime"]}]},{"anyOf":[{"required":["dates"]},{"required":["from","to"]}]}]}}]}},"required":["schedule"],"description":"Session scope only. Supply explicit dates, an inclusive from/to range with optional lowercase weekdays, or no date limits. Explicit dates cannot combine with daysOfWeek. At least one date, weekday, time point, or time window is required. Dated matching uses exact explicit attendance or weekly rules with exclusions; broad source date ranges do not establish exact attendance. At most 366 selected dates spanning at most 366 days; dates use years 0001–9999. Without date limits, known daily hours can satisfy a time-only request even when attendance dates are unknown; weekdays require actual non-excluded occurrences in the same configuration as requested hours. Omit clocks for date/weekday-only matching, supply atTime for a point, or supply startTime and a later endTime within the same day for a window. Point opening is inclusive and closing exclusive; adjacent windows do not overlap. Only endTime may be 24:00 or 24:00:00. Timed matching requires recorded daily source hours. Parent hours/time zones never fill unknown session facts."},"CanonicalFilterCapabilities":{"type":"object","additionalProperties":false,"properties":{"fields":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"field":{"type":"string"},"type":{"type":"string"},"description":{"type":"string","description":"Optional interpretation guidance for filter-only fields."},"operators":{"type":"array","items":{"type":"string","enum":["eq","in","missing","present","empty","lt","lte","gt","gte","containsText","matchesRegion","containsAny","containsAll","contains","overlaps"]}}},"required":["field","type","operators"]}},"relations":{"type":"object","additionalProperties":{"type":"string","enum":["organization","activity","session"]},"description":"Available relation name to target entity kind; preview disclosure can remove target kinds."},"predicates":{"type":"array","items":{"type":"string","enum":["all","any","field","related","radius","price","schedule"]}},"maximumDepth":{"type":"integer","minimum":1},"maximumPredicates":{"type":"integer","minimum":1,"description":"Total tree nodes, including groups."},"maximumValues":{"type":"integer","minimum":1},"maximumBytes":{"type":"integer","minimum":1},"missingMeans":{"const":"null"},"priceRequires":{"type":"array","items":{"type":"string"}},"scheduleTimeZones":{"type":"array","items":{"type":"string","enum":["venueLocal","IANA"]}}},"required":["fields","relations","predicates","maximumDepth","maximumPredicates","maximumValues","maximumBytes","missingMeans","priceRequires","scheduleTimeZones"],"description":"Live capability metadata for the disclosed entity projection. Current tree limits are 8192 encoded bytes, depth 8, 64 total nodes and 100 membership values. Field-specific typed values, relation availability, special-predicate field permissions and schedule constraints are still validated for every request."},"CanonicalSchemaColumn":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"dataType":{"type":"string"},"nullable":{"type":"boolean"}},"required":["name","dataType","nullable"]},"CanonicalSchemaTable":{"type":"object","properties":{"target_kind":{"type":"string","enum":["organization","activity","session"]},"table":{"type":"string"},"columns":{"type":"array","items":{"$ref":"#/components/schemas/CanonicalSchemaColumn"}},"filterCapabilities":{"$ref":"#/components/schemas/CanonicalFilterCapabilities"}},"required":["target_kind","table","columns"],"description":"Organization, activity, and session tables include filterCapabilities. Preview exposes only permitted entity tables and columns."},"CanonicalSchemaResponse":{"type":"object","properties":{"success":{"const":true},"data":{"type":"object","properties":{"tables":{"type":"array","items":{"$ref":"#/components/schemas/CanonicalSchemaTable"}},"tier":{"type":"string"},"sourceClass":{"const":"canonical"},"resultLimit":{"type":"integer","minimum":1},"preview":{"type":"object","properties":{"sample":{"const":true},"pageSize":{"type":"integer","minimum":1}},"required":["sample","pageSize"]}},"required":["tables","tier","sourceClass"]}},"required":["data","success"]},"HallPassRunSettings":{"type":"object","additionalProperties":false,"required":["spendCap","ordersEnabled","accountsEnabled","capabilities","capabilitiesUnavailable","savedCardsEnabled"],"description":"What a staff run may spend in this deployment, and whether it may attach site accounts.","properties":{"spendCap":{"$ref":"#/components/schemas/HallPassRunMoney"},"ordersEnabled":{"type":"boolean","description":"False when the cap is 0: only rehearsals start."},"accountsEnabled":{"type":"boolean","description":"True when the deployment holds HALLPASS_SECRETS_KEY, so a run may attach site accounts; false refuses any run that does."},"capabilities":{"anyOf":[{"$ref":"#/components/schemas/HallPassCapabilities"},{"type":"null"}]},"capabilitiesUnavailable":{"type":"boolean"},"savedCardsEnabled":{"type":"boolean","description":"True when HALLPASS_SAVED_CARDS is on with HALLPASS_SECRETS_KEY: staff may save cards and choose one for a run."}}},"HallPassRunActivity":{"type":"object","additionalProperties":false,"required":["lines","after"],"description":"A staff run's live log after a sequence, oldest first.","properties":{"lines":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["sequence","code","summary","at"],"properties":{"sequence":{"type":"integer","minimum":1},"code":{"type":"string"},"summary":{"type":"string"},"at":{"type":"string","format":"date-time"}}}},"after":{"type":"integer","minimum":0,"description":"The last sequence returned, or the one asked for when nothing is new."}}},"OverseerReviewSummary":{"type":"object","properties":{"id":{"format":"uuid","type":"string"},"version":{"minimum":1,"type":"integer"},"latestVersion":{"type":"integer","minimum":1},"kind":{"enum":["organization","activity","session"]},"label":{"type":"string"},"status":{"enum":["pending_approval","denied","applied","not_applied","revalidation_requested","reverting","reverted","revert_failed"]},"auditStatus":{"enum":["unreviewed","reviewed","changes_requested"]},"proposalCreatedAt":{"description":"Creation time of this immutable update version; createdAt is the identity creation time.","format":"date-time","type":"string"},"revalidationReason":{"type":"string"},"entityId":{"type":"string"},"activityId":{"type":"string"},"operation":{"enum":["create","update","remove"]}},"required":["id","version","latestVersion","kind","label","status","auditStatus","proposalCreatedAt","entityId","activityId","operation"]},"CanonicalStockImage":{"type":"object","additionalProperties":false,"description":"Public attribution for an illustrative stock photograph; it does not claim to depict the organization.","properties":{"provider":{"type":"string"},"photoId":{"type":"string"},"photoPageUrl":{"type":"string","format":"uri"},"photographerName":{"type":"string"},"photographerUrl":{"type":"string","format":"uri"},"licenseUrl":{"type":"string","format":"uri"}},"required":["provider","photoPageUrl","photographerName","licenseUrl"]},"CanonicalResolvedCover":{"type":"object","description":"Display cover resolved through the organization hierarchy. sourceEntity identifies its ultimate owner; inheritance does not add gallery membership. Stock is the final organization fallback.","properties":{"displayUrl":{"description":"Original remote URL or a protected relative organization brand-asset URL.","format":"uri-reference","type":"string"},"height":{"minimum":1,"type":["integer","null"]},"id":{"format":"uuid","type":"string"},"kind":{"type":"string"},"label":{"type":"string"},"width":{"minimum":1,"type":["integer","null"]},"isStock":{"type":"boolean","description":"Derived from retained stock metadata."},"stock":{"anyOf":[{"$ref":"#/components/schemas/CanonicalStockImage"},{"type":"null"}]},"sourceEntity":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"}},"required":["kind","id"]},"inherited":{"type":"boolean"}},"required":["id","displayUrl","label","kind","width","height","isStock","stock","sourceEntity","inherited"]},"OverseerStockImage":{"type":"object","additionalProperties":false,"description":"Immutable stock import metadata copied from the verified import receipt. Only organization gallery photos may carry it.","properties":{"provider":{"type":"string"},"photo_id":{"type":"string"},"photo_page_url":{"type":"string"},"photographer_name":{"type":"string"},"photographer_url":{"type":"string"},"license_url":{"type":"string"},"original_url":{"type":"string"},"sha256":{"type":"string"},"object_key":{"type":"string"},"imported_at":{"type":"string","format":"date-time"}},"required":["provider","photo_page_url","photographer_name","license_url","original_url","sha256","object_key","imported_at"]},"HallPassRunTraceEvent":{"type":"object","additionalProperties":false,"required":["id","at","kind","name"],"description":"One event about the run, as it was shipped to Axiom: one of OS Pilgrim's telemetry events about the run's Pilgrim task (already redacted of attached values), or one of Hallpass's or the API's rows about the run (`kind` `hallpass`, which carry `runId`, `decidedBy` and `sender` and neither `launch` nor `sequence`).","properties":{"id":{"type":"string","description":"The event id, to de-duplicate on: Pilgrim's launch and sequence, or Hallpass's `hallpass:<run id>:<seq>`."},"at":{"type":"string","format":"date-time"},"launch":{"type":"string","description":"Pilgrim's launch; absent on Hallpass rows."},"sequence":{"type":"integer","minimum":0,"description":"Pilgrim's sequence within the launch; absent on Hallpass rows, which order by `at`."},"kind":{"type":"string","enum":["launched","state","journal","helper","planner","pageTools","task","action","inference","decision","liveScreen","shipper","hallpass"]},"name":{"type":"string"},"outcome":{"type":"string","description":"Pilgrim's `succeeded` or `failed`; on Hallpass rows the run's final state (`settled`) or a decision's `accept` or `decline` (`decision_answered`)."},"code":{"type":"string","description":"A Pilgrim outcome code; on Hallpass rows a hold or decision code, `needs_person` on a stop for a person, and on `settled` the ending's cause (such as `confirmed`, `price_above_approval`, `pilgrim_blocked` or `pilgrim_stopped_paid`), whose failure code is in the event's value as `failure=`."},"seconds":{"type":"number"},"step":{"type":"integer"},"operation":{"type":"string"},"role":{"type":"string"},"model":{"type":"string"},"inputTokens":{"type":"integer"},"outputTokens":{"type":"integer"},"answer":{"type":"string"},"value":{"type":"string","description":"Free detail, such as what lay under a refused click (roles only), or Hallpass's `k=v` details (a decision and option id, an attempt, a reason keyword, a payment amount)."},"text":{"type":"string","description":"The run-log line, redacted by Pilgrim."},"phases":{"type":"object","additionalProperties":{"type":"number"},"description":"A helper operation's phase timings in seconds."},"attempt":{"type":"integer","description":"Pilgrim's attempt within the task, when the event carries one."},"turn":{"type":"string","description":"Pilgrim's planner turn id, when the event belongs to one."},"counts":{"type":"object","additionalProperties":{"type":"integer"},"description":"Pilgrim's counters at the event (for example clicks and refused clicks)."},"runId":{"type":"string","format":"uuid","description":"The staff run id; Hallpass rows only."},"decidedBy":{"type":"string","description":"Who acted, on Hallpass rows: `person`, `auto`, `key:<key id>` or, on `run_created`, `user:<user id>`."},"sender":{"type":"string","enum":["hallpass","apis"],"description":"Which service wrote the row; Hallpass rows only."},"route":{"type":"string","enum":["native","cua"]},"boundary":{"type":"string","enum":["public_inspection","signin_inspection","checkout_review","free_registration","purchase"]},"nativeAttemptId":{"type":"string"},"commandId":{"type":"string"},"leaseGeneration":{"type":"integer"}}},"HallPassRunTrace":{"type":"object","additionalProperties":false,"required":["source","events","after","complete"],"description":"A page of a staff run's trace, oldest first.","properties":{"taskId":{"type":"string","format":"uuid","description":"The Pilgrim task id behind the run, the `task` of its Pilgrim events; absent until Hallpass names one."},"source":{"type":"string","enum":["axiom"]},"events":{"type":"array","maxItems":1000,"items":{"$ref":"#/components/schemas/HallPassRunTraceEvent"}},"after":{"type":"string","description":"The last event's time, to pass back; empty when the page was empty and the request named no time."},"complete":{"type":"boolean","description":"False when the page was full and more may follow at once."}}},"CrawlerCampaignProfileStage":{"type":"object","required":["stage","count","error_count","total_ms","max_ms","active","oldest_active_ms"],"properties":{"stage":{"type":"string"},"count":{"type":"integer","minimum":0},"error_count":{"type":"integer","minimum":0},"total_ms":{"type":"number","minimum":0},"max_ms":{"type":"number","minimum":0},"active":{"type":"integer","minimum":0},"oldest_active_ms":{"type":"number","minimum":0},"cancelled_count":{"type":"integer","minimum":0}},"additionalProperties":false},"CrawlerCampaignProfileResources":{"type":"object","required":["memory_bytes","tasks"],"properties":{"cpu_cores":{"type":"number","minimum":0},"memory_bytes":{"type":"integer","minimum":0},"tasks":{"type":"integer","minimum":0}},"additionalProperties":false},"CrawlerCampaignRuntimeProfile":{"type":"object","required":["started_at","observed_at","running","stages","slots"],"properties":{"started_at":{"type":"string","format":"date-time"},"observed_at":{"type":"string","format":"date-time"},"running":{"type":"boolean"},"stages":{"type":"array","items":{"$ref":"#/components/schemas/CrawlerCampaignProfileStage"}},"slots":{"type":"object","additionalProperties":{"type":"integer","minimum":0}},"resources":{"$ref":"#/components/schemas/CrawlerCampaignProfileResources"},"models":{"type":"array","maxItems":8,"items":{"$ref":"#/components/schemas/CrawlerCampaignModelUsage"}}},"additionalProperties":false},"CrawlerCampaignWorkerProfile":{"type":"object","required":["worker_id","observed_at","stale","profile"],"properties":{"worker_id":{"type":"string","format":"uuid"},"observed_at":{"type":"string","format":"date-time"},"stale":{"type":"boolean"},"profile":{"$ref":"#/components/schemas/CrawlerCampaignRuntimeProfile"}},"additionalProperties":false},"CrawlerCampaignPerformance":{"type":"object","required":["workers"],"properties":{"workers":{"type":"array","items":{"$ref":"#/components/schemas/CrawlerCampaignWorkerProfile"}}},"additionalProperties":false},"HallPassRunReplay":{"type":"object","additionalProperties":false,"required":["kind","rehearsal","maxSpend","autoAcceptDecisions","deadlineMinutes","general","campBooking","accounts"],"description":"The request that starts this run again, in the shape `POST /api/hallpass/runs` takes, rebuilt from the run's task: send it with a new `idempotencyKey` to repeat the run exactly, or edit it first. `maxSpend` is the limit the run ran with (the staff cap applied when none was given) and `deadlineMinutes` the minutes it was given. Accounts come back as site and label only: usernames and passwords are sealed and never returned, so a replay attaches them afresh.","properties":{"kind":{"type":"string","enum":["general","camp_booking"],"description":"Defaults to `general`."},"rehearsal":{"type":"boolean","description":"True when the run spent nothing."},"maxSpend":{"oneOf":[{"$ref":"#/components/schemas/HallPassRunMoney"},{"type":"null"}],"description":"The limit the run ran with; null for a rehearsal."},"autoAcceptDecisions":{"type":"boolean","description":"Let Hallpass accept routine questions within the limit. Totals, signatures and provider accounts always wait for staff."},"deadlineMinutes":{"type":"integer","description":"The minutes the run was given."},"general":{"oneOf":[{"type":"object","additionalProperties":false,"required":["instruction"],"properties":{"instruction":{"type":"string","minLength":1,"maxLength":4000},"startUrl":{"type":["string","null"],"description":"An https page to start from."},"subjects":{"type":"array","maxItems":10,"items":{"type":"object","additionalProperties":false,"required":["role","fields"],"properties":{"role":{"type":"string","pattern":"^[a-z][a-z_]{0,31}$","description":"Such as `traveler` or `recipient`."},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Keys from the Hallpass subject vocabulary (`full_name`, `email_address`, `phone_number`, `street_address`, `postal_code`, `departure_airport`, …), as `/openapi/hallpass.json` lists them. Every field sent is shared with the provider."}}}},"answers":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["key","label","value"],"properties":{"key":{"type":"string"},"label":{"type":"string"},"value":{"type":["string","boolean","number"]}}}},"references":{"type":"array","maxItems":20,"items":{"type":"object","additionalProperties":false,"required":["kind","label","value"],"properties":{"kind":{"type":"string","enum":["url","note","catalog"]},"label":{"type":"string"},"value":{"type":"string"}}}}}},{"type":"null"}]},"campBooking":{"oneOf":[{"type":"object","additionalProperties":false,"required":["sessionId","attendee","guardian"],"properties":{"sessionId":{"type":"string","format":"uuid","description":"A catalog session; Hallpass gets its organization, dates, price and registration link."},"attendee":{"type":"object","additionalProperties":false,"required":["firstName","lastName"],"properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"birthDate":{"type":["string","null"],"format":"date"},"grade":{"type":["string","null"]},"allergies":{"type":["string","null"]}},"description":"Optional fields are null when the run did not carry them."},"guardian":{"type":"object","additionalProperties":false,"required":["name","email"],"properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":["string","null"]},"givenName":{"type":"string","minLength":1,"maxLength":128,"description":"Optional explicitly provided guardian name part. Never inferred from the full name."},"familyName":{"type":"string","minLength":1,"maxLength":128,"description":"Optional explicitly provided guardian name part. Never inferred from the full name."}}},"answers":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["key","label","value"],"properties":{"key":{"type":"string"},"label":{"type":"string"},"value":{"type":["string","boolean","number"]}}}},"allowWaitlist":{"type":"boolean"}}},{"type":"null"}]},"accounts":{"type":"array","description":"The attached accounts by site and label, in order; no username or password.","items":{"type":"object","additionalProperties":false,"required":["site","label"],"properties":{"site":{"type":"string","format":"uri"},"label":{"type":["string","null"]}}}},"execution":{"$ref":"#/components/schemas/HallPassExecutionPolicy","description":"Optional explicit route and stop boundary. Omitted on legacy runs; selected execution mode remains a separate fact."},"paymentCardId":{"type":"string","description":"The saved card the run was pinned to, when it named one. A replay sends it again; when that card has been removed or has expired, the new run is refused rather than falling back to the platform card."}}},"CrawlerWorkerJobCounts":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":128},"kind":{"type":"string","enum":["job","campaign"]},"status":{"type":"string","maxLength":64},"counts":{"type":"object","maxProperties":64,"additionalProperties":{"type":"integer","minimum":0}},"generation":{"type":"integer"},"settings_revision":{"type":"integer"},"name":{"type":"string","maxLength":1024,"description":"Optional operator-facing job or campaign label, bounded to 1024 UTF-8 bytes. No evidence, prompts, or logs."}},"required":["id","kind","status","counts"],"additionalProperties":false},"CrawlerWorkerStatus":{"type":"object","properties":{"worker_id":{"type":"string"},"origin":{"type":["string","null"],"format":"uri"},"observed_at":{"type":["string","null"],"format":"date-time"},"received_at":{"type":["string","null"],"format":"date-time"},"counts":{"type":["object","null"],"maxProperties":64,"additionalProperties":{"type":"integer","minimum":0},"description":"Latest worker totals. Null until a live or persisted snapshot is available; missing values are unknown, not zero. Always display observed_at when assessing freshness."},"jobs":{"type":"array","items":{"$ref":"#/components/schemas/CrawlerWorkerJobCounts"},"description":"Ordinary and mass job identity, optional name, status, revision, and numerical count summaries. Current workers publish the complete list; jobs_complete=false marks legacy incomplete payloads.","maxItems":100000},"persisted_at":{"type":["string","null"],"format":"date-time","description":"Last successful durable cache write. Independent of worker observation and API receipt time."},"source":{"type":"string","enum":["live","cache","none"],"description":"live is the last publication received in this API process; it may be stale. cache was restored after restart. none means no snapshot exists."},"jobs_complete":{"type":"boolean","description":"True only when the producer explicitly supplied all ordinary and mass job summaries. Legacy capped snapshots are false."},"controls":{"type":"array","maxItems":128,"items":{"$ref":"#/components/schemas/CrawlerWorkerControlStatus"},"description":"Recent volatile pause/stop requests and worker acknowledgements. Pending or unconfirmed does not establish that work stopped. API restart loses this queue; no commands are reconstructed from cached job counts."}},"required":["worker_id","origin","observed_at","received_at","counts","jobs","persisted_at","source","jobs_complete","controls"],"additionalProperties":false},"CrawlerWorkerAccessRequest":{"type":"object","properties":{"read_only":{"type":"boolean","default":false,"description":"Request a read-only capability. Loopback browser origins always receive read-only access."}},"required":[],"additionalProperties":false},"CrawlerWorkerAccess":{"type":"object","properties":{"worker_id":{"type":"string"},"origin":{"type":"string","format":"uri"},"token":{"type":"string","description":"Short-lived origin- and worker-bound Bearer capability; keep only in memory and never place in a URL."},"expires_at":{"type":"string","format":"date-time","description":"Expiry on the API's clock. Browsers schedule renewal from expires_in_seconds instead, because a viewer's clock may differ from the server's."},"expires_in_seconds":{"type":"integer","minimum":1,"description":"Capability lifetime measured from issue. Clients time renewal and request deadlines from when they requested access, never by comparing expires_at with their own clock."},"read_only":{"type":"boolean","description":"Actual capability scope, including server-enforced loopback restrictions. Disable controls whenever true."}},"required":["worker_id","origin","token","expires_at","expires_in_seconds","read_only"],"additionalProperties":false},"CrawlerWorkerPublisherSession":{"type":"object","properties":{"session":{"type":"string"},"worker_id":{"type":"string"}},"required":["session","worker_id"],"additionalProperties":false},"CrawlerWorkerCountSnapshot":{"type":"object","properties":{"session":{"type":"string"},"sequence":{"type":"integer","minimum":1},"observed_at":{"type":"string","format":"date-time"},"counts":{"type":"object","maxProperties":64,"additionalProperties":{"type":"integer","minimum":0}},"jobs":{"type":"array","maxItems":100000,"items":{"$ref":"#/components/schemas/CrawlerWorkerJobCounts"}},"jobs_complete":{"type":"boolean","default":false,"description":"New workers set true and include every job. Missing or false identifies a legacy incomplete snapshot. True requires no positive omitted_job_summaries count."}},"required":["observed_at","counts","jobs","jobs_complete"],"additionalProperties":false},"CrawlerWorkerReadySnapshot":{"type":"object","properties":{"session":{"type":"string"},"sequence":{"type":"integer","minimum":1},"urls":{"type":"array","maxItems":500000,"items":{"type":"string","format":"uri"}}},"required":["session","sequence","urls"],"additionalProperties":false},"CrawlerWorkerCountsAccepted":{"type":"object","properties":{"accepted":{"type":"integer","minimum":1}},"required":["accepted"],"additionalProperties":false},"CrawlerWorkerReadyAccepted":{"type":"object","properties":{"count":{"type":"integer","minimum":0},"sequence":{"type":"integer","minimum":1}},"required":["count","sequence"],"additionalProperties":false},"HallPassRunNotice":{"type":"object","additionalProperties":false,"required":["decisionId","channel","purpose","outcome","attempts","at"],"description":"One way a run's decision reached its requester away from the run page, or the moment the run page showed it. The workers send each waiting decision once on every configured channel (`raised`) and again when half its window has passed (`reminder`); the run page records a browser notification it raised (`web`, `raised`, `shown`) and the first time its requester saw the decision (`web`, `seen`, `seen`).","properties":{"decisionId":{"type":"string"},"channel":{"type":"string","enum":["email","slack","web"],"x-open-enum":true},"purpose":{"type":"string","enum":["raised","reminder","seen"],"x-open-enum":true},"outcome":{"type":"string","enum":["sent","failed","skipped","shown","seen"],"x-open-enum":true,"description":"`failed` means the latest attempt failed; a notice is tried three times in all, with growing waits. `skipped` means the requester has no address on the channel."},"attempts":{"type":"integer","minimum":1},"at":{"type":"string","format":"date-time","description":"When the latest attempt or the page's record happened."}}},"OverseerFeedbackItem":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["request","correction"]},"text":{"type":"string"},"actorId":{"type":"string"},"status":{"type":"string","enum":["queued","in_progress","blocked","completed","cancelled","unknown","superseded"]},"progress":{"type":"string"},"organizationId":{"type":["string","null"],"format":"uuid"},"organizationName":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"requestId":{"type":["string","null"],"format":"uuid"},"reviewId":{"type":["string","null"],"format":"uuid"},"reviewVersion":{"type":["integer","null"],"minimum":1},"resolvedReviewVersion":{"type":["integer","null"],"minimum":1},"entityConfigurationId":{"type":["string","null"],"format":"uuid","description":"Supplied configuration ID for a request; corrections resolve the original version entity configuration. No target kind is persisted."},"field":{"type":["string","null"],"description":"Catalog field key the request is about, such as refund_policy, or media for the gallery, cover and logo. Null for whole-record requests and for corrections."},"targetKind":{"type":["string","null"],"enum":["organization","activity","session",null],"description":"Server-resolved canonical record kind. Null when no unambiguous record is available."},"targetId":{"type":["string","null"],"format":"uuid","description":"Server-resolved exact canonical record ID for existing canonical tools."},"targetName":{"type":["string","null"],"description":"Display label for the exact canonical target."}},"required":["id","kind","text","actorId","status","progress","organizationId","organizationName","createdAt","updatedAt","requestId","reviewId","reviewVersion","resolvedReviewVersion","entityConfigurationId","field","targetKind","targetId","targetName"]},"OverseerFeedbackPage":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OverseerFeedbackItem"}},"nextBefore":{"type":["string","null"]},"hasMore":{"type":"boolean"}},"required":["items","nextBefore","hasMore"]},"CrawlerWorkerControlRequest":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"worker_id":{"type":"string"},"kind":{"type":"string","enum":["job","campaign"]},"job_id":{"type":"string"},"action":{"type":"string","enum":["pause","cancel"]},"generation":{"type":"integer","minimum":0},"settings_revision":{"type":"integer","minimum":0}},"required":["id","worker_id","kind","job_id","action","generation","settings_revision"],"additionalProperties":false},"CrawlerWorkerControlReceipt":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["applied","rejected","expired"]},"message":{"type":"string"},"completed_at":{"type":"string","format":"date-time"}},"required":["id","status","message","completed_at"],"additionalProperties":false},"CrawlerWorkerControlCommand":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"worker_id":{"type":"string"},"kind":{"type":"string","enum":["job","campaign"]},"job_id":{"type":"string"},"action":{"type":"string","enum":["pause","cancel"]},"generation":{"type":"integer","minimum":0},"settings_revision":{"type":"integer","minimum":0},"actor":{"type":"string"},"requested_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"}},"required":["id","worker_id","kind","job_id","action","generation","settings_revision","actor","requested_at","expires_at"],"additionalProperties":false},"CrawlerWorkerControlStatus":{"type":"object","properties":{"request":{"$ref":"#/components/schemas/CrawlerWorkerControlRequest"},"status":{"type":"string","enum":["pending","applied","rejected","expired","unconfirmed"]},"message":{"type":"string"},"requested_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"}},"required":["request","status","message","requested_at","expires_at","completed_at"],"additionalProperties":false},"CrawlerWorkerControlExchange":{"type":"object","properties":{"session":{"type":"string"},"receipts":{"type":["array","null"],"maxItems":16,"items":{"$ref":"#/components/schemas/CrawlerWorkerControlReceipt"}}},"required":["session","receipts"],"additionalProperties":false},"CrawlerWorkerControlDelivery":{"type":"object","properties":{"commands":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/CrawlerWorkerControlCommand"}}},"required":["commands"],"additionalProperties":false},"OverseerRemovalEntity":{"type":"object","properties":{"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"}},"required":["kind","id"]},"OverseerRemovalMember":{"type":"object","properties":{"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"baseline":{"type":"object","description":"Immutable canonical record, configuration, relationships, media, and supporting values captured by the server."}},"required":["kind","id","name","baseline"]},"OverseerRemovalSnapshot":{"type":"object","properties":{"survivor":{"$ref":"#/components/schemas/OverseerRemovalMember","readOnly":true,"description":"Optional server-captured active same-kind survivor outside the removed subtree. Approval rechecks this baseline and records only the removed root mapping; data, descendants, and history stay in place."},"version":{"type":"integer","minimum":1},"root":{"$ref":"#/components/schemas/OverseerRemovalEntity"},"members":{"type":"array","items":{"$ref":"#/components/schemas/OverseerRemovalMember"},"description":"The target and all affected descendants, including independently removed descendants whose removal is not owned by this proposal."}},"required":["version","root","members"]},"OverseerReviewRemoval":{"type":"object","properties":{"reason":{"type":"string","description":"Explicit rationale for removal supported by captured sources, canonical comparisons, or the server-captured survivor."},"snapshot":{"$ref":"#/components/schemas/OverseerRemovalSnapshot","readOnly":true},"comparisons":{"type":"array","items":{"$ref":"#/components/schemas/OverseerRemovalMember"},"readOnly":true,"description":"Immutable values of comparison records captured when the proposal was saved."}},"required":["reason","comparisons"]},"CrawlerCampaignModelUsage":{"type":"object","required":["provider","model","responses","input_tokens","output_tokens","cache_read_input_tokens","cache_creation_input_tokens"],"description":"Reported model responses since worker process start. Input excludes cache reads/writes; output includes reported reasoning. Reused saved results are not counted again.","properties":{"provider":{"type":"string","enum":["codex","anthropic"]},"model":{"type":"string"},"responses":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991},"input_tokens":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991},"output_tokens":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991},"cache_read_input_tokens":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991},"cache_creation_input_tokens":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991}}},"HallPassExecutionPolicy":{"type":"object","additionalProperties":false,"required":["route","boundary"],"properties":{"route":{"type":"string","enum":["native","cua"]},"fallback":{"type":"boolean","default":false,"description":"Native to CUA only, before a checkpoint; both must support the same boundary."},"boundary":{"type":"string","enum":["public_inspection","signin_inspection","checkout_review","free_registration","purchase"]}}},"HallPassCapabilities":{"type":"object","required":["version","revision","observedAt","routes"],"properties":{"version":{"type":"string","enum":["hallpass-capabilities-v1"]},"revision":{"type":"string"},"observedAt":{"type":"string","format":"date-time"},"routes":{"type":"array","items":{"type":"object","required":["route","mode","configured","profile","boundaries","accounts","liveView","targetScope","disabledReason"],"properties":{"route":{"type":"string","enum":["native","cua"]},"mode":{"type":"string","enum":["fast","automatic"]},"configured":{"type":"boolean"},"profile":{"type":"string"},"boundaries":{"type":"array","items":{"type":"string","enum":["public_inspection","signin_inspection","checkout_review","free_registration","purchase"]}},"accounts":{"type":"boolean"},"liveView":{"type":"boolean"},"targetScope":{"type":"string"},"disabledReason":{"type":"string"},"registration":{"$ref":"#/components/schemas/CampRegistrationCapability"}}}},"paymentCard":{"type":"object","description":"The platform card a run that may spend pays with, as display-safe facts only. Absent when the Hallpass broker has no platform card. Never carries the number, security code, postal code or cardholder name.","required":["reference","brand","lastFour","expirationMonth","expirationYear","cardholderInitials"],"properties":{"reference":{"type":"string"},"brand":{"type":"string","enum":["visa","mastercard","amex","discover","jcb","diners","unionpay","card"]},"lastFour":{"type":"string"},"expirationMonth":{"type":"integer"},"expirationYear":{"type":"integer"},"cardholderInitials":{"type":"string"}}},"observations":{"type":"array","description":"What the broker's own exchanges with each executor showed, outside revision. Absent until an exchange finished; never derived from configuration.","items":{"type":"object","required":["route","executor","state","observedAt"],"properties":{"route":{"type":"string","enum":["native","cua"]},"executor":{"type":"string","description":"The executor the broker talks to, e.g. pilgrim."},"state":{"type":"string","enum":["reachable","key_rejected","unreachable","failing"],"description":"The latest exchange's outcome at observedAt."},"observedAt":{"type":"string","format":"date-time"},"reachedAt":{"type":"string","format":"date-time","description":"The latest exchange the executor answered with this broker's credential."},"statusAt":{"type":"string","format":"date-time","description":"When the executor last reported status; present with enabled and busy."},"enabled":{"type":"boolean"},"busy":{"type":"boolean","description":"Another sender's task was current at statusAt."}}}}},"description":"Current broker configuration support, not downstream health or evidence of provider booking success. Null/unavailable must not enable explicit routing."},"OverseerFeedbackDecisionInput":{"type":"object","additionalProperties":false,"properties":{"requestId":{"type":"string","format":"uuid"},"expectedUpdatedAt":{"type":"string","format":"date-time","description":"Exact updatedAt from the displayed feedback item. Stale decisions conflict without being applied."},"action":{"type":"string","enum":["retry","cancel"]},"comments":{"type":"string","description":"Optional additional retry guidance, at most 8000 UTF-8 bytes. Combined original feedback, separator and comments must fit 16384 bytes for requests or 8000 for corrections. Nonempty comments are invalid for cancel."}},"required":["requestId","expectedUpdatedAt","action"]},"OverseerFeedbackDecisionReceipt":{"type":"object","additionalProperties":false,"properties":{"requestId":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["request","correction"]},"id":{"type":"string"},"action":{"type":"string","enum":["retry","cancel"]}},"required":["requestId","kind","id","action"]},"OverseerRejectedFeedbackDecision":{"type":"object","properties":{"error":{"const":true},"message":{"type":"string"},"outcome":{"const":"rejected"},"requestId":{"type":"string","format":"uuid"}},"required":["error","message","outcome","requestId"]},"PilgrimRelease":{"type":"object","additionalProperties":false,"required":["id","kind","commit","sha256","byteSize","format","toolchain","signing","provenance"],"properties":{"id":{"type":"string"},"kind":{"const":"source"},"commit":{"type":"string"},"sha256":{"type":"string"},"byteSize":{"type":"integer","minimum":1,"maximum":16777216},"format":{"const":"git_bundle"},"toolchain":{"type":"object","additionalProperties":false,"required":["schema","state","channel","xcodeVersion","xcodeBuild","swiftVersion","sdkVersion","minimumHostVersion","codeResourcesSHA256","archiveSHA256"],"properties":{"schema":{"type":"integer"},"state":{"type":"string"},"channel":{"type":"string"},"xcodeVersion":{"type":"string"},"xcodeBuild":{"type":"string"},"swiftVersion":{"type":"string"},"sdkVersion":{"type":"string"},"minimumHostVersion":{"type":"string"},"codeResourcesSHA256":{"type":"string"},"archiveSHA256":{"type":"string"}}},"signing":{"const":"node_local"},"provenance":{"type":"object","additionalProperties":false,"required":["repository","branch","checkRunId","buildRunId","workflowPath"],"properties":{"repository":{"type":"string"},"branch":{"type":"string"},"checkRunId":{"type":"string"},"buildRunId":{"type":"string"},"workflowPath":{"type":"string"}}}}},"PilgrimClaimRequest":{"type":"object","additionalProperties":false,"required":["generation","attemptId"],"properties":{"generation":{"type":"string"},"attemptId":{"type":"string"}}},"PilgrimClaim":{"type":"object","additionalProperties":false,"required":["generation","attemptId","fence","leaseExpiresAt","resumeRequired"],"properties":{"generation":{"type":"string"},"attemptId":{"type":"string"},"fence":{"type":"string"},"leaseExpiresAt":{"type":"string"},"resumeRequired":{"type":"boolean"}}},"PilgrimProgress":{"type":"object","additionalProperties":false,"required":["generation","attemptId","fence","sequence","phase","observedAt","installedReceipt","health","reasonCode"],"properties":{"generation":{"type":"string"},"attemptId":{"type":"string"},"fence":{"type":"string"},"sequence":{"type":"string"},"phase":{"type":"string","enum":["queued","downloading","building","awaiting_signing","deferred_busy","installing","verifying","healthy","rolled_back","failed","outcome_unknown","superseded","cancelled"]},"observedAt":{"type":"string"},"installedReceipt":{"anyOf":[{"$ref":"#/components/schemas/PilgrimReceipt"},{"type":"null"}]},"health":{"anyOf":[{"$ref":"#/components/schemas/PilgrimHealth"},{"type":"null"}]},"reasonCode":{"anyOf":[{"type":"string","enum":["bundle_unavailable","bundle_digest_mismatch","toolchain_mismatch","signing_locked","app_busy","safe_update_unavailable","signature_invalid","signer_mismatch","install_failed","verification_failed","api_not_listening","rollback_failed","updater_interrupted","outcome_unknown"]},{"type":"null"}]},"stageStartedAt":{"type":"string","format":"date-time","description":"Updater-clock start of the current stage; omitted by updaters that predate it. Not after observedAt."},"durations":{"type":"object","additionalProperties":false,"description":"Cumulative milliseconds spent in each completed stage of this attempt.","properties":{"downloading":{"type":"integer","minimum":0,"maximum":2592000000},"building":{"type":"integer","minimum":0,"maximum":2592000000},"awaiting_signing":{"type":"integer","minimum":0,"maximum":2592000000},"deferred_busy":{"type":"integer","minimum":0,"maximum":2592000000},"installing":{"type":"integer","minimum":0,"maximum":2592000000},"verifying":{"type":"integer","minimum":0,"maximum":2592000000}}},"detail":{"type":"string","minLength":1,"maxLength":2048,"description":"Updater-authored failure or wait explanation, at most 2048 bytes, no control characters other than newline and tab. Allowed only when phase is failed, rolled_back, awaiting_signing or deferred_busy. Stored for staff; never logged or shipped to Axiom."}}},"PilgrimProgressAck":{"type":"object","additionalProperties":false,"required":["generation","fence","sequence","accepted","duplicate","leaseExpiresAt"],"properties":{"generation":{"type":"string"},"fence":{"type":"string"},"sequence":{"type":"string"},"accepted":{"type":"boolean"},"duplicate":{"type":"boolean"},"leaseExpiresAt":{"type":"string"}}},"PilgrimReceipt":{"type":"object","additionalProperties":false,"required":["generation","releaseId","commit","sourceSHA256","signerSHA1","executableSHA256","installedAt","buildVerified","testsVerified","toolchainCodeResourcesSHA256"],"properties":{"generation":{"anyOf":[{"type":"string"},{"type":"null"}]},"releaseId":{"anyOf":[{"type":"string"},{"type":"null"}]},"commit":{"type":"string"},"sourceSHA256":{"anyOf":[{"type":"string"},{"type":"null"}]},"signerSHA1":{"type":"string"},"executableSHA256":{"type":"string"},"installedAt":{"type":"string"},"buildVerified":{"type":"boolean"},"testsVerified":{"type":"boolean"},"toolchainCodeResourcesSHA256":{"anyOf":[{"type":"string"},{"type":"null"}]}}},"PilgrimCreateRequest":{"type":"object","additionalProperties":false,"required":["requestId","releaseId","rollbackOf"],"properties":{"requestId":{"type":"string"},"releaseId":{"type":"string"},"rollbackOf":{"anyOf":[{"type":"string"},{"type":"null"}]}}},"PilgrimActionRequest":{"type":"object","additionalProperties":false,"required":["requestId"],"properties":{"requestId":{"type":"string"}}},"PilgrimHealth":{"type":"object","additionalProperties":false,"required":["observedAt","processPresent","heartbeatFresh","listenerRunning","executableSHA256"],"properties":{"observedAt":{"type":"string"},"processPresent":{"type":"boolean"},"heartbeatFresh":{"type":"boolean"},"listenerRunning":{"type":"boolean"},"listenerSource":{"anyOf":[{"type":"string","enum":["probe","telemetry"]},{"type":"null"}]},"executableSHA256":{"type":"string"},"signingKeychainInSearchList":{"type":"boolean","description":"Whether the Pilgrim signing keychain is in the user keychain search list when health was observed. It belongs there only while the sign stage runs, so true on a healthy node is drift left by an interrupted sign stage. Absent from updaters that do not report it."}}},"PilgrimAssignment":{"type":"object","additionalProperties":false,"required":["generation","release","cancelRequested","supersededBy"],"properties":{"generation":{"type":"string"},"release":{"$ref":"#/components/schemas/PilgrimRelease"},"cancelRequested":{"type":"boolean"},"supersededBy":{"anyOf":[{"type":"string"},{"type":"null"}]}}},"PilgrimActive":{"type":"object","additionalProperties":false,"required":["generation","release","cancelRequested","supersededBy","attemptId","fence","leaseExpiresAt","resumeRequired","sequence","phase"],"properties":{"generation":{"type":"string"},"release":{"$ref":"#/components/schemas/PilgrimRelease"},"cancelRequested":{"type":"boolean"},"supersededBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"attemptId":{"type":"string"},"fence":{"type":"string"},"leaseExpiresAt":{"type":"string"},"resumeRequired":{"type":"boolean"},"sequence":{"type":"string"},"phase":{"type":"string"}}},"PilgrimDesired":{"type":"object","additionalProperties":false,"required":["nodeId","desired","active","pollAfterSeconds"],"properties":{"nodeId":{"type":"string","enum":["pilgrim-a","pilgrim-b","pilgrim-c","pilgrim-d"]},"desired":{"anyOf":[{"$ref":"#/components/schemas/PilgrimAssignment"},{"type":"null"}]},"active":{"anyOf":[{"$ref":"#/components/schemas/PilgrimActive"},{"type":"null"}]},"pollAfterSeconds":{"type":"integer"}}},"PilgrimDeployment":{"type":"object","additionalProperties":false,"required":["generation","release","createdAt","promotedAt","cancelledAt","rollbackOf","actor","sourceAvailable"],"properties":{"generation":{"type":"string"},"release":{"$ref":"#/components/schemas/PilgrimRelease"},"createdAt":{"type":"string"},"promotedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"cancelledAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"rollbackOf":{"anyOf":[{"type":"string"},{"type":"null"}]},"actor":{"type":"string"},"sourceAvailable":{"type":"boolean"}}},"PilgrimNodeState":{"type":"object","additionalProperties":false,"required":["nodeId","desired","active","lastReportedAt","installedReceipt","health","phase","reasonCode","lastContactAt","timeline"],"properties":{"nodeId":{"type":"string"},"desired":{"anyOf":[{"$ref":"#/components/schemas/PilgrimAssignment"},{"type":"null"}]},"active":{"anyOf":[{"$ref":"#/components/schemas/PilgrimActive"},{"type":"null"}]},"lastReportedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"installedReceipt":{"anyOf":[{"$ref":"#/components/schemas/PilgrimReceipt"},{"type":"null"}]},"health":{"anyOf":[{"$ref":"#/components/schemas/PilgrimHealth"},{"type":"null"}]},"phase":{"type":"string"},"reasonCode":{"type":["string","null"]},"lastContactAt":{"type":["string","null"]},"timeline":{"type":"array","maxItems":50,"description":"The current attempt's audited transitions and its generation's deploy, promote and cancel actions, oldest first.","items":{"$ref":"#/components/schemas/PilgrimTimelineEntry"}}}},"PilgrimTimelineEntry":{"type":"object","additionalProperties":false,"required":["generation","phase","action","at","stageStartedAt","durationMs","reasonCode","actor","detail"],"properties":{"generation":{"type":"string"},"phase":{"description":"Node phase this transition entered; null for a staff or CI action.","anyOf":[{"type":"string","enum":["queued","downloading","building","awaiting_signing","deferred_busy","installing","verifying","healthy","rolled_back","failed","outcome_unknown","superseded","cancelled"]},{"type":"null"}]},"action":{"description":"Deploy, promote or cancel; null for a node transition.","anyOf":[{"type":"string","enum":["deploy","promote","cancel"]},{"type":"null"}]},"at":{"type":"string","format":"date-time"},"stageStartedAt":{"type":["string","null"],"format":"date-time"},"durationMs":{"type":["integer","null"],"minimum":0},"reasonCode":{"type":["string","null"]},"actor":{"description":"\"staff:<account id>\" or \"ci\" on actions; null on node transitions.","type":["string","null"]},"detail":{"type":["string","null"]}}},"PilgrimFleet":{"type":"object","additionalProperties":false,"required":["deployments","nodes","events"],"properties":{"deployments":{"type":"array","items":{"$ref":"#/components/schemas/PilgrimDeployment"}},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/PilgrimNodeState"},"minItems":4,"maxItems":4},"events":{"type":"array","items":{"$ref":"#/components/schemas/PilgrimEvent"}},"publisher":{"anyOf":[{"$ref":"#/components/schemas/PilgrimPublisher"},{"type":"null"}]}}},"PilgrimEvent":{"type":"object","additionalProperties":false,"properties":{"generation":{"type":"string"},"nodeId":{"type":["string","null"]},"kind":{"type":"string"},"actor":{"type":"string"},"createdAt":{"type":"string"}},"required":["generation","nodeId","kind","actor","createdAt"]},"FamilyGroupPerson":{"type":"object","required":["userId","publicName","publicNameKnown","photoUrl"],"properties":{"userId":{"type":"string","format":"uuid"},"publicName":{"type":["string","null"]},"publicNameKnown":{"type":"boolean","description":"False when the parent has no name to show; publicName is then null and the client writes its own wording."},"photoUrl":{"type":["string","null"],"description":"The photo the parent uploaded (an API path carrying its hash), else their Google picture (an absolute URL); null when there is neither."}},"additionalProperties":false,"description":"A parent as other parents see them: public name and photo, never the email."},"FamilyGroupLastMessage":{"type":"object","required":["author","sender","text","createdAt"],"properties":{"author":{"enum":["parent","assistant"]},"sender":{"oneOf":[{"$ref":"#/components/schemas/FamilyGroupPerson"},{"type":"null"}]},"text":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}},"additionalProperties":false,"description":"The group's newest message the viewer can read."},"FamilyGroup":{"type":"object","required":["id","name","owned","memberCount","lastSeq","lastReadSeq","unread","lastMessage","joinedAt","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":1,"maxLength":80},"memberCount":{"type":"integer","minimum":1},"lastSeq":{"type":"integer","minimum":0,"description":"The seq of the group's newest message."},"lastReadSeq":{"type":"integer","minimum":0,"description":"The newest message the viewer has read."},"unread":{"type":"integer","minimum":0,"maximum":100,"description":"Messages from others after lastReadSeq, counted up to 100."},"lastMessage":{"oneOf":[{"$ref":"#/components/schemas/FamilyGroupLastMessage"},{"type":"null"}]},"joinedAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"owned":{"type":"boolean","description":"You own the group, so you may rename and delete it. The owner is its creator while they are a member, else its longest-standing member."}},"additionalProperties":false},"FamilyGroupMember":{"type":"object","required":["userId","publicName","publicNameKnown","photoUrl","joinedAt"],"properties":{"userId":{"type":"string","format":"uuid"},"publicName":{"type":["string","null"]},"publicNameKnown":{"type":"boolean","description":"False when the parent has no name to show; publicName is then null and the client writes its own wording."},"photoUrl":{"type":["string","null"],"description":"The photo the parent uploaded (an API path carrying its hash), else their Google picture (an absolute URL); null when there is neither."},"joinedAt":{"type":"string","format":"date-time"}},"additionalProperties":false},"FamilyGroupSentInvitation":{"type":"object","required":["id","invitee","invitedBy","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"invitee":{"$ref":"#/components/schemas/FamilyGroupPerson"},"invitedBy":{"$ref":"#/components/schemas/FamilyGroupPerson"},"createdAt":{"type":"string","format":"date-time"}},"additionalProperties":false},"FamilyGroupDetail":{"type":"object","required":["id","name","owned","memberCount","lastSeq","lastReadSeq","unread","lastMessage","joinedAt","createdAt","members","invitations"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":1,"maxLength":80},"memberCount":{"type":"integer","minimum":1},"lastSeq":{"type":"integer","minimum":0,"description":"The seq of the group's newest message."},"lastReadSeq":{"type":"integer","minimum":0,"description":"The newest message the viewer has read."},"unread":{"type":"integer","minimum":0,"maximum":100,"description":"Messages from others after lastReadSeq, counted up to 100."},"lastMessage":{"oneOf":[{"$ref":"#/components/schemas/FamilyGroupLastMessage"},{"type":"null"}]},"joinedAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"members":{"type":"array","items":{"$ref":"#/components/schemas/FamilyGroupMember"},"description":"In the order they joined."},"invitations":{"type":"array","items":{"$ref":"#/components/schemas/FamilyGroupSentInvitation"},"description":"Open invitations, oldest first."},"owned":{"type":"boolean","description":"You own the group, so you may rename and delete it. The owner is its creator while they are a member, else its longest-standing member."}},"additionalProperties":false},"FamilyGroupInvitation":{"type":"object","required":["id","groupId","groupName","memberCount","invitedBy","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"groupId":{"type":"string","format":"uuid"},"groupName":{"type":"string"},"memberCount":{"type":"integer","minimum":1},"invitedBy":{"$ref":"#/components/schemas/FamilyGroupPerson"},"createdAt":{"type":"string","format":"date-time"}},"additionalProperties":false},"FamilyGroupMessage":{"type":"object","required":["id","seq","author","sender","mine","clientMessageId","text","assistantStatus","replyToMessageId","assistant","shared","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"seq":{"type":"integer","minimum":1,"description":"The message's place in the group: every member sees one order."},"author":{"enum":["parent","assistant"]},"sender":{"oneOf":[{"$ref":"#/components/schemas/FamilyGroupPerson"},{"type":"null"}]},"mine":{"type":"boolean"},"clientMessageId":{"type":["string","null"],"description":"Your own message's idempotency key; null on others' messages."},"text":{"type":"string","description":"The sender's words; empty on a share sent without a note."},"assistantStatus":{"enum":["pending","answered","failed",null],"description":"For a parent's message that asked Assistant: pending until its reply arrives; null otherwise."},"replyToMessageId":{"type":["string","null"],"format":"uuid","description":"On Assistant's reply, the question it answers."},"assistant":{"type":["object","null"],"description":"On Assistant's reply: failed, and when it answered its results, sources, limitations and suggestedReplies as POST /api/assistant returns them.","additionalProperties":true},"createdAt":{"type":"string","format":"date-time"},"shared":{"oneOf":[{"$ref":"#/components/schemas/FamilyGroupShared"},{"type":"null"}]}},"additionalProperties":false},"FamilyGroupMessages":{"type":"object","required":["messages","latestSeq","lastReadSeq","hasEarlier","assistantPending"],"properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/FamilyGroupMessage"},"description":"Oldest first."},"latestSeq":{"type":"integer","minimum":0,"description":"The group's newest seq: poll with after=latestSeq."},"lastReadSeq":{"type":"integer","minimum":0},"hasEarlier":{"type":"boolean","description":"Older messages you can read exist before this page."},"assistantPending":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Questions Assistant is still answering. One that leaves this list without a reply was lost."}},"additionalProperties":false},"FamilyChildDocument":{"type":"object","required":["id","childId","title","fileName","contentType","byteSize","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"childId":{"type":"string","format":"uuid"},"title":{"type":"string","minLength":1,"maxLength":120},"fileName":{"type":"string","minLength":1,"maxLength":200},"contentType":{"enum":["application/pdf","image/jpeg","image/png"]},"byteSize":{"type":"integer","minimum":1},"createdAt":{"type":"string","format":"date-time"}},"additionalProperties":false},"FamilyProviderAccount":{"type":"object","additionalProperties":false,"required":["platform","provider","site","usernameDisplay","createdAt","lastUsedAt"],"properties":{"platform":{"type":"string","enum":["coursestorm"],"x-open-enum":true},"provider":{"type":"string","description":"The provider's name for display, such as CourseStorm."},"site":{"type":"string","format":"uri"},"usernameDisplay":{"type":"string","description":"Masked, such as r…@example.com."},"createdAt":{"type":"string","format":"date-time","description":"When Hallpass created the account at the provider and kept it for the adult."},"lastUsedAt":{"type":["string","null"],"format":"date-time","description":"When a later booking last attached it."}}},"ScoringMethodology":{"type":"object","description":"The running organization scoring rubric. Numeric policy and worked examples come from the definitions used by the scoring engine.","properties":{"scoringVersion":{"type":"string","description":"Version of the running organization scoring rubric."},"title":{"type":"string","description":"Human-readable methodology title."},"description":{"type":"string","description":"What the score measures and its limits."},"criteria":{"type":"array","description":"Scored factors and their indicators.","items":{"$ref":"#/components/schemas/ScoringMethodologyCriterion"}},"inputs":{"type":"array","description":"How canonical records and evidence become scoring inputs.","items":{"$ref":"#/components/schemas/ScoringMethodologyInput"}},"fields":{"type":"array","description":"The field ledger, including relative weights, decay policy, and canonical derivation.","items":{"$ref":"#/components/schemas/ScoringMethodologyField"}},"sources":{"type":"array","description":"Evidence source kinds and authority fractions.","items":{"$ref":"#/components/schemas/ScoringMethodologySource"}},"caps":{"type":"array","description":"Conditions that limit the overall organization score.","items":{"$ref":"#/components/schemas/ScoringMethodologyCap"}},"calculation":{"type":"array","description":"Steps that produce the overall organization score.","items":{"$ref":"#/components/schemas/ScoringMethodologyStep"}},"confidence":{"type":"array","description":"Steps that produce the confidence measure and displayed uncertainty range.","items":{"$ref":"#/components/schemas/ScoringMethodologyStep"}},"bands":{"type":"array","description":"Display bands and their inclusive lower score bounds.","items":{"$ref":"#/components/schemas/ScoringMethodologyBand"}},"notes":{"type":"array","description":"Interpretation limits and evidence caveats.","items":{"type":"string"}}},"required":["scoringVersion","title","description","criteria","inputs","fields","sources","caps","calculation","confidence","bands","notes"],"additionalProperties":false},"ScoringMethodologyCriterion":{"type":"object","description":"A scored factor. Weights are relative within the overall calculation.","properties":{"key":{"type":"string","description":"Stable factor key."},"label":{"type":"string","description":"Human-readable factor label."},"question":{"type":"string","description":"The question the factor answers."},"weight":{"type":"number","description":"Relative weight in the overall calculation.","minimum":0},"indicators":{"type":"array","description":"Rules combined to calculate this factor.","items":{"$ref":"#/components/schemas/ScoringMethodologyIndicator"}}},"required":["key","label","question","weight","indicators"],"additionalProperties":false},"ScoringMethodologyIndicator":{"type":"object","description":"One scoring rule and examples evaluated by the running scoring engine.","properties":{"key":{"type":"string","description":"Indicator key within its factor."},"label":{"type":"string","description":"Human-readable indicator label."},"weight":{"type":"number","description":"Relative weight within the factor.","minimum":0},"description":{"type":"string","description":"Evidence the indicator measures."},"formula":{"type":"string","description":"Formula using the running numeric policy."},"missing":{"type":"string","description":"Treatment of absent or unknown evidence."},"inputs":{"type":"array","description":"Scoring input keys consumed by this indicator.","items":{"type":"string"}},"examples":{"type":"array","description":"Worked examples expressed as indicator points.","items":{"$ref":"#/components/schemas/ScoringMethodologyExample"}}},"required":["key","label","weight","description","formula","missing","inputs","examples"],"additionalProperties":false},"ScoringMethodologyExample":{"type":"object","description":"A worked scoring example.","properties":{"label":{"type":"string","description":"Example evidence and circumstances."},"score":{"type":"number","description":"Indicator points after clamping and rounding.","minimum":0,"maximum":100}},"required":["label","score"],"additionalProperties":false},"ScoringMethodologyInput":{"type":"object","description":"Derivation and missing-value handling for one scoring input.","properties":{"key":{"type":"string","description":"Scoring input key."},"label":{"type":"string","description":"Human-readable input label."},"columns":{"type":"array","description":"Canonical columns and evidence used to derive the input.","items":{"type":"string"}},"derivation":{"type":"string","description":"How the input is assembled."},"missing":{"type":"string","description":"Treatment of absent or unknown input."}},"required":["key","label","columns","derivation","missing"],"additionalProperties":false},"ScoringMethodologyField":{"type":"object","description":"A field in the scoring ledger and the policy used to interpret it.","properties":{"key":{"type":"string","description":"Scoring field key."},"label":{"type":"string","description":"Human-readable field label."},"group":{"type":"string","description":"Field group used in completeness calculations."},"weight":{"type":"number","description":"Relative field weight.","minimum":0},"halfLifeDays":{"type":"number","description":"Default half-life in days; contextual rules may override the half-life or freeze age at session completion.","minimum":0},"columns":{"type":"array","description":"Canonical columns supplying this field.","items":{"type":"string"}},"derivation":{"type":"string","description":"How presence and observations are derived."},"validation":{"type":"string","description":"Validation that contributes evidence for this field."},"freshnessPolicy":{"type":"string","enum":["registration"],"description":"Optional contextual freshness policy; omitted for fixed half-lives."},"freshnessRule":{"type":"string","description":"Human-readable freshness rule, including registration and completed-session behavior."}},"required":["key","label","group","weight","halfLifeDays","columns","derivation","validation","freshnessRule"],"additionalProperties":false},"ScoringMethodologySource":{"type":"object","description":"Authority assigned to an evidence source kind.","properties":{"kind":{"type":"string","description":"Source kind key."},"label":{"type":"string","description":"Human-readable source kind label."},"authority":{"type":"number","description":"Source authority fraction.","minimum":0,"maximum":1}},"required":["kind","label","authority"],"additionalProperties":false},"ScoringMethodologyCap":{"type":"object","description":"A condition that limits the overall score.","properties":{"key":{"type":"string","description":"Score limit key."},"criterion":{"type":"string","description":"Factor responsible for the limit."},"ceiling":{"type":"number","description":"Maximum overall score when the condition applies.","minimum":0,"maximum":100},"condition":{"type":"string","description":"Evidence condition that activates the limit."}},"required":["key","criterion","ceiling","condition"],"additionalProperties":false},"ScoringMethodologyStep":{"type":"object","description":"One step in the score or confidence calculation.","properties":{"key":{"type":"string","description":"Calculation step key."},"label":{"type":"string","description":"Human-readable step label."},"formula":{"type":"string","description":"Formula using the running numeric policy."},"description":{"type":"string","description":"How the formula is interpreted."}},"required":["key","label","formula","description"],"additionalProperties":false},"ScoringMethodologyBand":{"type":"object","description":"A display band for an overall score.","properties":{"key":{"type":"string","description":"Band key."},"label":{"type":"string","description":"Human-readable band label."},"minimum":{"type":"number","description":"Inclusive lower overall score bound for this band.","minimum":0,"maximum":100}},"required":["key","label","minimum"],"additionalProperties":false},"ScoringMethodologyResponse":{"type":"object","description":"Successful methodology response.","properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ScoringMethodology"}},"required":["success","data"],"additionalProperties":false},"CanonicalFieldSource":{"type":"object","additionalProperties":false,"required":["kind","id"],"properties":{"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"}},"description":"The canonical entity supplying this effective field value or configuration unit."},"CanonicalFieldResolution":{"type":"object","additionalProperties":{"type":"string","enum":["own","inherited","unset"]},"description":"Map of projected field names to their current effective resolution. An explicit clear on an inheritable configuration unit is own."},"CanonicalFieldSources":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/CanonicalFieldSource"},"description":"Map of projected field names to the entity supplying the effective value or configuration unit. Unset fields have no entry."},"CanonicalOwnValues":{"type":"object","additionalProperties":true,"description":"Map of editable field names to values stored on this entity; null entries are retained."},"PilgrimHostSnapshot":{"type":"object","additionalProperties":false,"required":["observedAt","bootAt","tailscaleAddress","graphicalSession","automaticLogin","pilgrimLaunchAtLogin","sshListening","screenSharingListening","tailscaleRunning","tailscaleKeepAlive","applicationRunning","heartbeatFresh","listenerRunning","applicationLaunchId","plannerConnected","assistanceEnabled","managerSHA256"],"properties":{"observedAt":{"type":"string","format":"date-time"},"bootAt":{"type":"string","format":"date-time"},"tailscaleAddress":{"type":"string"},"graphicalSession":{"type":"boolean"},"automaticLogin":{"type":"boolean"},"pilgrimLaunchAtLogin":{"type":"boolean"},"sshListening":{"type":"boolean"},"screenSharingListening":{"type":"boolean"},"tailscaleRunning":{"type":"boolean"},"tailscaleKeepAlive":{"type":"boolean"},"applicationRunning":{"type":"boolean"},"heartbeatFresh":{"type":"boolean"},"listenerRunning":{"type":"boolean"},"applicationLaunchId":{"anyOf":[{"type":"string"},{"type":"null"}]},"plannerConnected":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"assistanceEnabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"managerSHA256":{"type":"string","pattern":"^[a-f0-9]{64}$"}}},"PilgrimNodeOperation":{"type":"object","additionalProperties":false,"required":["id","nodeId","action","actor","createdAt","expiresAt","state","attemptId","claimedAt","completedAt","reason","beforeBootAt","beforeLaunchId"],"properties":{"id":{"type":"string","format":"uuid"},"nodeId":{"type":"string","enum":["pilgrim-a","pilgrim-b","pilgrim-c","pilgrim-d"]},"action":{"type":"string","enum":["restart_pilgrim","reboot"]},"actor":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"state":{"type":"string","enum":["queued","running","succeeded","failed","expired","outcome_unknown"]},"attemptId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"claimedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"completedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"reason":{"anyOf":[{"type":"string"},{"type":"null"}]},"beforeBootAt":{"type":"string","format":"date-time"},"beforeLaunchId":{"anyOf":[{"type":"string"},{"type":"null"}]}}},"PilgrimManagedNode":{"type":"object","additionalProperties":false,"required":["nodeId","snapshot","receivedAt","operations"],"properties":{"nodeId":{"type":"string","enum":["pilgrim-a","pilgrim-b","pilgrim-c","pilgrim-d"]},"snapshot":{"anyOf":[{"$ref":"#/components/schemas/PilgrimHostSnapshot"},{"type":"null"}]},"receivedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"operations":{"type":"array","items":{"$ref":"#/components/schemas/PilgrimNodeOperation"},"maxItems":10}}},"PilgrimManagementFleet":{"type":"object","additionalProperties":false,"required":["nodes"],"properties":{"nodes":{"type":"array","items":{"$ref":"#/components/schemas/PilgrimManagedNode"},"minItems":4,"maxItems":4}}},"PilgrimNodeOperationRequest":{"type":"object","additionalProperties":false,"required":["requestId","action"],"properties":{"requestId":{"type":"string","format":"uuid"},"action":{"type":"string","enum":["restart_pilgrim","reboot"]}}},"PilgrimNodeOperationResult":{"type":"object","additionalProperties":false,"required":["id","attemptId","state","reason"],"properties":{"id":{"type":"string","format":"uuid"},"attemptId":{"type":"string","format":"uuid"},"state":{"type":"string","enum":["succeeded","failed","outcome_unknown"]},"reason":{"anyOf":[{"type":"string"},{"type":"null"}]}}},"PilgrimManagementReport":{"type":"object","additionalProperties":false,"required":["snapshot","result"],"properties":{"snapshot":{"$ref":"#/components/schemas/PilgrimHostSnapshot"},"result":{"anyOf":[{"$ref":"#/components/schemas/PilgrimNodeOperationResult"},{"type":"null"}]}}},"PilgrimOperationClaimRequest":{"type":"object","additionalProperties":false,"required":["attemptId"],"properties":{"attemptId":{"type":"string","format":"uuid"}}},"SessionBookingRequirement":{"type":"object","additionalProperties":false,"required":["key","label","type","requirement","knowledge","source_url","quote","observed_at"],"properties":{"key":{"type":"string"},"label":{"type":"string"},"type":{"type":"string","enum":["text","date","number","boolean","choice","document","acknowledgement"]},"requirement":{"type":"string","enum":["required","optional","conditional","unknown"]},"knowledge":{"type":"string","enum":["expected","confirmed"]},"condition":{"type":"string"},"stage":{"type":"string"},"profile_field":{"type":"string"},"source_url":{"type":"string","format":"uri"},"quote":{"type":"string"},"observed_at":{"type":"string","format":"date-time"}}},"SessionBookingFields":{"type":"object","additionalProperties":false,"required":["version","coverage","fields"],"description":"Session-owned requirement definitions, never family answers. Partial coverage may omit requirements. Complete coverage requires all recorded items to be confirmed; it does not grant booking consent.","properties":{"version":{"type":"integer","enum":[1]},"coverage":{"type":"string","enum":["partial","complete"]},"fields":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/SessionBookingRequirement"}}}},"PilgrimQualification":{"type":"object","additionalProperties":false,"required":["runId","nodeId","taskId","startedAt","completedAt","securityCodeReplied","purchaseDeclined","queueClear","informationRemoved","telemetryVerified","totalCents"],"properties":{"runId":{"type":"string"},"nodeId":{"const":"pilgrim-a"},"taskId":{"type":"string"},"startedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"securityCodeReplied":{"type":"boolean"},"purchaseDeclined":{"type":"boolean"},"queueClear":{"type":"boolean"},"informationRemoved":{"type":"boolean"},"telemetryVerified":{"type":"boolean"},"totalCents":{"type":"integer"}}},"PilgrimPublisherReport":{"type":"object","additionalProperties":false,"required":["phase"],"properties":{"phase":{"enum":["watching","waiting_ci","building","staging","publishing","awaiting_a","qualifying_a","promoting","awaiting_fleet","healthy","failed"]},"commit":{"type":"string"},"generation":{"type":"string"},"artifactSHA256":{"type":"string"},"githubDeploymentId":{"type":"string"},"reasonCode":{"type":"string"},"qualification":{"$ref":"#/components/schemas/PilgrimQualification"}}},"PilgrimPublisher":{"type":"object","additionalProperties":false,"required":["paused","report","updatedAt"],"properties":{"paused":{"type":"boolean"},"report":{"$ref":"#/components/schemas/PilgrimPublisherReport"},"updatedAt":{"type":"string","format":"date-time"}}},"PilgrimPublisherPauseRequest":{"type":"object","additionalProperties":false,"required":["paused"],"properties":{"requestId":{"type":"string"},"paused":{"type":"boolean"}}},"HallPassBatch":{"type":"object","additionalProperties":false,"required":["id","requesterUserId","name","state","parallelism","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"requesterUserId":{"oneOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"name":{"type":"string"},"state":{"type":"string","enum":["running","paused","cancelled"]},"parallelism":{"type":"integer","minimum":1,"maximum":8},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"HallPassBatchImport":{"type":"object","additionalProperties":false,"required":["batchId","name","csv","parallelism"],"properties":{"batchId":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":1,"maxLength":120},"csv":{"type":"string"},"parallelism":{"type":"integer","minimum":1,"maximum":8},"allowRegistrations":{"type":"boolean"}}},"HallPassBatchPreviewInput":{"type":"object","additionalProperties":false,"required":["batchId","csv"],"properties":{"batchId":{"type":"string","format":"uuid"},"csv":{"type":"string"}}},"HallPassBatchPreviewRow":{"type":"object","additionalProperties":false,"required":["row","externalId","startUrl","boundary","instruction","attendeeName","attendeeEmail","country","executor"],"properties":{"row":{"type":"integer"},"externalId":{"type":"string"},"startUrl":{"type":"string"},"boundary":{"type":"string","enum":["public_inspection","checkout_review","free_registration"]},"instruction":{"type":"string"},"attendeeName":{"type":"string"},"attendeeEmail":{"type":"string"},"country":{"type":"string"},"executor":{"type":"string","enum":["native","cua"],"description":"The executor requested at import. Actual execution mode remains separate."}}},"HallPassBatchCSVProblem":{"type":"object","additionalProperties":false,"required":["row","column","message"],"properties":{"row":{"type":"integer"},"column":{"type":"string"},"message":{"type":"string"}}},"HallPassBatchPreview":{"type":"object","additionalProperties":false,"required":["batchId","rows","problems"],"properties":{"batchId":{"type":"string","format":"uuid"},"rows":{"type":"array","items":{"$ref":"#/components/schemas/HallPassBatchPreviewRow"}},"problems":{"type":"array","items":{"$ref":"#/components/schemas/HallPassBatchCSVProblem"}}}},"HallPassBatchCounts":{"type":"object","additionalProperties":false,"required":["total","queued","dispatching","active","finished","blocked","cancelled","confirmed","failed","needsPerson","uncertain","refused"],"properties":{"total":{"type":"integer","minimum":0},"queued":{"type":"integer","minimum":0},"dispatching":{"type":"integer","minimum":0},"active":{"type":"integer","minimum":0},"finished":{"type":"integer","minimum":0},"blocked":{"type":"integer","minimum":0},"cancelled":{"type":"integer","minimum":0},"confirmed":{"type":"integer","minimum":0},"failed":{"type":"integer","minimum":0},"needsPerson":{"type":"integer","minimum":0},"uncertain":{"type":"integer","minimum":0},"refused":{"type":"integer","minimum":0}}},"HallPassBatchJob":{"type":"object","additionalProperties":false,"required":["batchId","position","externalId","runId","state","attempts","error","reused","updatedAt","startUrl","boundary","runState","mode","stage","total","startedAt","endedAt","executor"],"properties":{"batchId":{"type":"string","format":"uuid"},"position":{"type":"integer"},"externalId":{"type":"string"},"runId":{"oneOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"state":{"type":"string","enum":["queued","dispatching","active","finished","blocked","cancelled"]},"attempts":{"type":"integer"},"error":{"oneOf":[{"type":"string"},{"type":"null"}]},"reused":{"type":"boolean"},"updatedAt":{"type":"string","format":"date-time"},"startUrl":{"type":"string"},"boundary":{"type":"string","enum":["public_inspection","checkout_review","free_registration"]},"runState":{"oneOf":[{"type":"string"},{"type":"null"}]},"mode":{"oneOf":[{"type":"string","enum":["fast","automatic"]},{"type":"null"}]},"stage":{"oneOf":[{"type":"string"},{"type":"null"}]},"total":{"oneOf":[{"$ref":"#/components/schemas/HallPassRunMoney"},{"type":"null"}]},"startedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"endedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"executor":{"type":"string","enum":["native","cua"],"description":"The executor requested at import. Actual execution mode remains separate."}}},"HallPassBatchConfiguration":{"type":"object","additionalProperties":false,"required":["state","parallelism"],"properties":{"state":{"type":"string","enum":["running","paused","cancelled"]},"parallelism":{"type":"integer","minimum":1,"maximum":8}}},"HallPassSavedCard":{"type":"object","additionalProperties":false,"description":"A staff-saved payment card as any read shows it: display facts only. The number, cardholder name, postal code and expiry are sealed under HALLPASS_SECRETS_KEY and never returned; the security code is never stored.","required":["id","label","brand","lastFour","expirationMonth","expirationYear","cardholderInitials","createdAt","lastUsedAt"],"properties":{"id":{"type":"string","description":"The card's opaque reference (`saved-card-<uuid>`), the value a run's `paymentCardId` and a receipt's `paymentCardReference` carry."},"label":{"anyOf":[{"type":"string"},{"type":"null"}]},"brand":{"type":"string","enum":["visa","mastercard","amex","discover","jcb","diners","unionpay","card"]},"lastFour":{"type":"string"},"expirationMonth":{"type":"integer"},"expirationYear":{"type":"integer"},"cardholderInitials":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]}}},"HallPassSavedCardInput":{"type":"object","additionalProperties":false,"description":"A card a signed-in staff member saves. Validated (Luhn, 13 to 19 digits, unexpired) and sealed before it is stored; never logged or echoed.","required":["number","expirationMonth","expirationYear","cardholder","postalCode"],"properties":{"number":{"type":"string"},"expirationMonth":{"type":"integer","minimum":1,"maximum":12},"expirationYear":{"type":"integer","minimum":2000,"maximum":2099},"cardholder":{"type":"string","maxLength":128},"postalCode":{"type":"string","maxLength":16},"label":{"type":"string","maxLength":80}}},"AssistantEmailSource":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","pattern":"^[A-Fa-f0-9]{1,128}$"},"threadId":{"type":"string","pattern":"^[A-Fa-f0-9]{1,128}$"},"subject":{"type":"string"},"from":{"type":"string"},"date":{"type":"string"},"snippet":{"type":"string"},"url":{"type":"string","description":"Server-built Gmail message permalink, only https://mail.google.com/mail/u/?authuser=<connected address>#all/<threadId>. No email HTML is rendered."}},"required":["id","threadId","subject","from","date","snippet","url"]},"AssistantEmailDraft":{"type":["object","null"],"additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"from":{"type":"string"},"to":{"type":"string","maxLength":254},"subject":{"type":"string","maxLength":300},"body":{"type":"string","maxLength":12000},"expiresAt":{"type":"string","format":"date-time"}},"required":["id","from","to","subject","body","expiresAt"],"description":"Application-only editable email review. Not sent and not stored as a Gmail draft. The id is an account-owned, one-use review ticket bound to the current Gmail connection; expires after thirty minutes."},"AssistantEmailPrompt":{"type":["object","null"],"additionalProperties":false,"description":"Optional contextual Gmail step produced only by the private adult assistant’s offer_gmail tool, when email could resolve a specific school-date, existing booking or provider-reply goal. No Gmail access happens until the parent explicitly chooses the client action. Null when source messages or a draft already answer the turn; never shown in a group. The client selects Connect Gmail or Check my email from actual connection state. Historical answers may omit this field.","required":["reason"],"properties":{"reason":{"type":"string","minLength":1,"maxLength":280,"description":"One trimmed plain line explaining the useful missing fact. No URLs, control or format characters, connection URL, button payload or arbitrary action."}}},"FamilyVerification":{"type":"object","required":["verifiedAt","claimed","claimedAt","sourceDomain"],"additionalProperties":false,"properties":{"verifiedAt":{"type":["string","null"],"format":"date-time","description":"When School's Out staff checked the organization; null when unverified."},"claimed":{"type":"boolean","description":"Whether the organization has an active member (an approved claim)."},"claimedAt":{"type":["string","null"],"format":"date-time","description":"When its earliest active membership began (the approval); null when unclaimed."},"sourceDomain":{"type":["string","null"],"description":"The host of its website (websiteUrl), the site an unclaimed listing was built from; null without one."}}},"FamilyFormatKind":{"type":"string","enum":["day_camp","overnight_camp","class","drop_in","event"],"description":"An activity's format in the family vocabulary. overnight_camp, not overnight: overnight is a day length."},"FamilyLocation":{"type":"object","required":["id","name","areaName","address","latitude","longitude","coordinatePrecision","activityIds"],"additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid","description":"Stable across reads: derived from the organization and its location's point (the point its venues state most often, venues within 30 m of one another being one location); activities name it as locationId."},"name":{"type":["string","null"],"description":"What tells it apart from the organization's other locations: the first of its areaName, its street (its street line without the house number) and its street line that no other location of the organization shares; its areaName when none does."},"areaName":{"type":["string","null"],"description":"The neighborhood the venue is in, else its city."},"address":{"type":["string","null"],"description":"The most common street address (street line, city) its venues state; the street line is address line 1, else the first part of the address text."},"latitude":{"type":"number"},"longitude":{"type":"number"},"coordinatePrecision":{"type":"string","enum":["exact","building","parcel","street"]},"activityIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"The live activities that meet there, in id order; empty for the organization's own address."}}},"FamilyActivityHost":{"type":"object","additionalProperties":false,"required":["id","name","verification","logo_url","logoLoadable","brandColors","logoGround"],"description":"The organization that runs an activity, as the activity page's Hosted by reads it: its logo and colors exactly as its own rows read them.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"verification":{"$ref":"#/components/schemas/FamilyVerification"},"logo_url":{"type":["string","null"],"format":"uri","description":"The organization's selected logo, as its own rows show it (null without one)."},"logoLoadable":{"type":"boolean","description":"Whether logo_url is the public logo route."},"brandColors":{"oneOf":[{"$ref":"#/components/schemas/FamilyBrandColors"},{"type":"null"}]},"logoGround":{"oneOf":[{"$ref":"#/components/schemas/FamilyLogoGround"},{"type":"null"}]},"summary":{"type":["string","null"],"description":"The organization's one line (summary)."}}},"FamilyCatalogDetailSession":{"type":"object","additionalProperties":false,"description":"One of an organization's or activity's first live sessions on a family detail read (sessions[], in start order, up to sessionsLimit), with its availability and the booking additions a session row carries.","required":["id","name","start_date","end_date","availability","registrationUrl","spotsLeft","bookingPlatform","registrationMode","agentBookable"],"properties":{"id":{"type":"string","format":"uuid"},"activity_id":{"type":["string","null"],"format":"uuid","description":"Organization detail only: the session's activity."},"name":{"type":["string","null"]},"start_date":{"type":["string","null"]},"end_date":{"type":["string","null"]},"availability":{"$ref":"#/components/schemas/FamilyCatalogAvailability"},"registrationUrl":{"type":["string","null"],"format":"uri","description":"Where a family registers for this session, as a session row's registrationUrl."},"spotsLeft":{"type":["integer","null"],"minimum":1,"description":"The places left, set exactly when availability is limited, else null."},"bookingPlatform":{"type":["string","null"],"description":"The booking platform the session's booking target is on, as a session row's bookingPlatform."},"registrationMode":{"$ref":"#/components/schemas/FamilyRegistrationMode"},"agentBookable":{"$ref":"#/components/schemas/FamilyAgentBookable"}}},"FamilyAgentBookable":{"type":"boolean","description":"Sessions, nextSessions entries and detail sessions: whether a family can ask Hallpass (Macy) to book this session: bookings are on, the session has a booking target (its recorded https booking_url without credentials, else its confirmed link; never a supporting link: the URL Hallpass opens), its prices are in USD, and Hallpass has not been refused by the provider at that target's host since its last booking there went through. false means Book on their site."},"FamilyFamiliesBooked":{"type":"object","required":["count","season","radiusMiles"],"additionalProperties":false,"properties":{"count":{"type":"integer","minimum":5,"description":"The families signed up, rounded down to a step (public.families_booked_shown_count) and never below the privacy floor: a lower bound, never the exact number, so one family's sign-up changes it only across a step."},"season":{"type":"string","enum":["winter","spring","summer","fall"]},"radiusMiles":{"type":["integer","null"],"description":"Set when only families near the viewer's area count: families placed within this many miles of the viewer's coarse search-area cell, each family placed by its own cell too."}}},"FamilyRegistrationMode":{"type":["string","null"],"enum":["instant","application",null],"description":"Sessions only: how the camp registers: instant (book a place) or application (apply; the camp answers later). The stated mode (registration_mode, inherited from the activity, then the organization) wins; else as Hallpass receipts of earlier bookings at the session (or, for a session of an activity, at any of the activity's sessions) showed it; null when unknown (read as Book, never as a known instant)."},"FamilyOffer":{"type":"object","required":["key","offered"],"properties":{"key":{"type":"string","enum":["before_care","after_care","lunch","snacks","transportation","wheelchair_access","parking","sibling_discount","sliding_scale","swimming"]},"offered":{"type":"boolean","description":"A definite yes or no; an unknown key is absent."},"detail":{"type":"string","maxLength":140}}},"FamilyBrandColors":{"type":"object","additionalProperties":false,"description":"The brand colors a parent sees for an organization: the business's brand profile palette (source business), else the record's brand_color (source record), else the palette suggested from its logo's pixels (source logo). Logo colors are worked out once per logo rendition with the site Brand System's sampling rules, so a page sampling the same logo in the browser agrees; a declared primary without a declared secondary takes the logo's companion color, as the site does. The site treats business and record colors as the camp's own and logo colors as suggested.","required":["primary","secondary","accents","logoSurface","source"],"properties":{"primary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"secondary":{"type":["string","null"],"pattern":"^#[0-9a-f]{6}$"},"accents":{"type":"array","items":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"description":"Further brand colors; empty when there are none."},"logoSurface":{"type":["string","null"],"pattern":"^#[0-9a-f]{6}$","description":"The ground the business declared for its logo (its brand profile's background color); null when it declared none. The sampled ground is logoGround."},"source":{"type":"string","enum":["business","record","logo"]}}},"FamilyLogoGround":{"type":"object","additionalProperties":false,"description":"Where the logo's pixels say it belongs, the site Brand System's LogoGround: a transparent, mostly light logo needs a dark ground; any other transparent logo sits on a light ground; an opaque logo whose edge is one flat color keeps that color (edge, with color).","required":["kind"],"properties":{"kind":{"type":"string","enum":["light","dark","edge"]},"color":{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"The edge color; only for kind edge."}}},"BookingProfileField":{"type":"string","enum":["child.firstName","child.lastName","child.birthDate","child.grade","child.gradeSchoolYear","child.allergies","child.registrationNotes","child.reportedAge","guardian.fullName","guardian.givenName","guardian.familyName","guardian.email","guardian.phone","emergencyContact.name","emergencyContact.phone"]},"BookingProfileChild":{"type":"object","additionalProperties":false,"properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"birthDate":{"type":"string"},"grade":{"type":"string"},"gradeSchoolYear":{"type":"string"},"allergies":{"type":"string"},"registrationNotes":{"type":"string"},"reportedAge":{"type":"object","additionalProperties":false,"properties":{"years":{"type":"integer","minimum":0},"asOf":{"type":"string","format":"date"}},"required":["years","asOf"]}},"required":["firstName","lastName"]},"BookingProfileGuardian":{"type":"object","additionalProperties":false,"properties":{"fullName":{"type":"string"},"givenName":{"type":"string"},"familyName":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}},"required":["fullName","email"]},"BookingProfileValues":{"type":"object","additionalProperties":false,"properties":{"child":{"$ref":"#/components/schemas/BookingProfileChild"},"guardian":{"$ref":"#/components/schemas/BookingProfileGuardian"},"emergencyContact":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"phone":{"type":"string"}},"required":[]}},"required":["child","guardian"]},"BookingProfileSource":{"type":"object","additionalProperties":false,"properties":{"childId":{"type":"string"},"childVersion":{"type":"integer","minimum":1},"guardianId":{"type":"string"},"guardianRevision":{"type":"string"},"capturedAt":{"type":"string","format":"date-time"}},"required":["childId","childVersion","guardianId","guardianRevision","capturedAt"]},"BookingProfileEvidence":{"type":"object","additionalProperties":false,"properties":{"authorizationRef":{"type":"string"},"policyVersion":{"type":"string"},"permittedFields":{"type":"array","items":{"$ref":"#/components/schemas/BookingProfileField"},"uniqueItems":true},"provenance":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/FamilyProvenance"}}},"required":["authorizationRef","policyVersion","permittedFields","provenance"]},"BookingProfileSnapshotV1":{"type":"object","additionalProperties":false,"properties":{"schemaVersion":{"type":"integer","const":1},"source":{"$ref":"#/components/schemas/BookingProfileSource"},"values":{"$ref":"#/components/schemas/BookingProfileValues"},"evidence":{"$ref":"#/components/schemas/BookingProfileEvidence"}},"required":["schemaVersion","source","values","evidence"]},"FamilyFeedNotePose":{"type":"object","additionalProperties":false,"description":"How Mellow is drawn beside the note, from the time of the read in the family's time zone.","properties":{"time":{"type":"string","enum":["morning","afternoon","evening","late"],"description":"morning from 5 am, afternoon from noon, evening from 5 pm, late from 9 pm."},"season":{"type":"string","enum":["spring","summer","autumn","winter"]}},"required":["time","season"]},"FamilyFeedSavedNote":{"type":"object","additionalProperties":false,"description":"A line in the adult's Notes.","properties":{"date":{"type":"string","format":"date"},"id":{"type":"string"},"type":{"type":"string","enum":["quote","fact","welcome","staff"]},"text":{"type":"string"},"credit":{"type":["string","null"],"description":"A quote's author and book, shown under it: \"Lewis Carroll, Through the Looking-Glass\". Null for a fact, a welcome line, or a staff line."},"doodle":{"type":"string","enum":["air","art","animals","festival","food","general","library","music","park","rain","roads","school","science","seasonal","snow","sports","sun","theater","transit","water"]},"savedAt":{"type":"string","format":"date-time"}},"required":["date","id","type","text","credit","doodle","savedAt"]},"FamilyEmailCheckUnresolved":{"type":"object","additionalProperties":false,"required":["messageId","organizationId","organizationName","sessionId","activityId","reason"],"description":"A registration-like email from an organization the viewer showed interest in that the check did not record. Ask the parent with the client's own \"Did you register?\" flow.","properties":{"messageId":{"type":"string","description":"The Gmail message id."},"organizationId":{"type":"string","format":"uuid"},"organizationName":{"type":"string"},"sessionId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"The matched session when the reason is child; null otherwise."},"activityId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"reason":{"enum":["session","child"],"description":"session: no single upcoming session matched. child: the email names none of the viewer's children and they see more than one."}}},"FamilyExpoPushSubscriptionInput":{"type":"object","additionalProperties":false,"description":"A native app's Expo push token, from expo-notifications getExpoPushTokenAsync().","properties":{"kind":{"const":"expo","type":"string"},"token":{"type":"string","pattern":"^(ExponentPushToken|ExpoPushToken)\\[[A-Za-z0-9_-]{1,200}\\]$","description":"The Expo push token, ExponentPushToken[...]."},"platform":{"type":"string","enum":["ios","android"]},"deviceName":{"type":["string","null"],"maxLength":100,"description":"Optional; shown in the family's own list. Blank is null."}},"required":["kind","token","platform"]},"FamilyExpoPushSubscription":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"kind":{"const":"expo","type":"string"},"token":{"type":"string"},"platform":{"type":"string","enum":["ios","android"]},"deviceName":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","description":"When the sign-in session first subscribed."},"updatedAt":{"type":"string","format":"date-time","description":"When the sign-in session last subscribed."}},"required":["id","kind","token","platform","deviceName","createdAt","updatedAt"]},"OnboardingPoint":{"type":"object","additionalProperties":false,"required":["latitude","longitude"],"properties":{"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180}},"description":"A WGS 84 point."},"OnboardingSchool":{"type":"object","additionalProperties":false,"description":"A school from the school directory, as signed-out onboarding shows it. Never a street address.","required":["id","name","area","postalCode","district","point","calendarStatus"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"area":{"type":"string","description":"City and state, \"Portland, OR\"."},"postalCode":{"type":["string","null"]},"district":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["id","name"],"properties":{"id":{"type":"string","description":"The district's NCES LEAID."},"name":{"type":["string","null"],"description":"The name the district's school calendar publishes under; null when School's Out has no calendar source for the district."}}}]},"point":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OnboardingPoint"}]},"calendarStatus":{"type":"string","enum":["published","pending","none"],"description":"published: days off for the current term are known; pending: a calendar source is linked but has published nothing yet; none: no calendar source."}}},"OnboardingPlace":{"type":"object","additionalProperties":false,"description":"An area signed-out onboarding can look for camps around. A street address search answers with the area it is in; the point is rounded to three decimals.","required":["placeId","label","postalCode","region","point","served"],"properties":{"placeId":{"type":"string","description":"Opaque and stateless: accepted by POST /api/onboarding/picks as area.placeId. It stands for the point."},"label":{"type":"string","description":"\"Sellwood, Portland, OR\"."},"postalCode":{"type":["string","null"]},"region":{"type":["string","null"],"description":"Two-letter US state code."},"point":{"$ref":"#/components/schemas/OnboardingPoint"},"served":{"type":"boolean","description":"Inside an area School's Out serves."}}},"OnboardingPick":{"type":"object","additionalProperties":false,"description":"One activity signed-out onboarding suggests: only the fields a tile shows. No contact, street address, link, capacity or provenance field.","required":["activityId","organizationId","name","organizationName","logoUrl","imageUrl","ages","nextSession","priceText","point","distanceMiles","because"],"properties":{"activityId":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"name":{"type":"string"},"organizationName":{"type":"string"},"logoUrl":{"type":["string","null"],"description":"The organization's selected logo."},"imageUrl":{"type":["string","null"],"description":"The activity's resolved cover (its own selected cover, else one inherited from its sessions or organization), else its first photo; null when it has none, and the app fills the tile."},"ages":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["min","max"],"properties":{"min":{"type":"integer"},"max":{"type":"integer"}},"description":"Audience in whole years, both ends inclusive."}]},"nextSession":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["id","start","end"],"properties":{"id":{"type":"string","format":"uuid"},"start":{"type":["string","null"],"format":"date"},"end":{"type":["string","null"],"format":"date"}}}]},"priceText":{"type":["string","null"],"description":"The lowest US dollar price in words, \"From $40 a day\"; null for no price or another currency."},"point":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OnboardingPoint"}],"description":"Where the activity is, as the signed-out catalog shows it."},"distanceMiles":{"type":["number","null"],"description":"From the area's point."},"because":{"oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["child","interest"],"properties":{"child":{"type":"integer","minimum":0,"description":"The child's position in the request's children."},"interest":{"type":["string","null"],"description":"The child's interest id the activity matches; null when it fits only their age."}}}],"description":"The first child the pick fits by age and interest, else by age alone."}}},"OnboardingPicksInput":{"type":"object","additionalProperties":false,"required":["area","children"],"properties":{"area":{"type":"object","additionalProperties":false,"minProperties":1,"maxProperties":1,"description":"Exactly one of a placeId from GET /api/onboarding/places, a ZIP code, or a point.","properties":{"placeId":{"type":"string"},"postalCode":{"type":"string","pattern":"^[0-9]{5}$"},"point":{"$ref":"#/components/schemas/OnboardingPoint"}}},"schoolIds":{"type":"array","maxItems":4,"items":{"type":"string","format":"uuid"},"description":"Schools from GET /api/onboarding/schools whose days off to return."},"children":{"type":"array","minItems":1,"maxItems":6,"items":{"type":"object","additionalProperties":false,"description":"One child. The age comes from birthday, else age, else grade (grade + 5); one is required, for an age from 0 to 17.","properties":{"birthday":{"type":"string","format":"date"},"age":{"type":"integer","minimum":0,"maximum":17},"grade":{"type":"integer","minimum":-1,"maximum":12,"description":"-1 pre-K, 0 kindergarten, 1 to 12."},"interests":{"type":"array","maxItems":10,"items":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,31}$"},"description":"Feed interest ids, as the family catalog's interest filter takes them."}}}},"limit":{"type":"integer","minimum":1,"maximum":8,"default":8}}},"FamilyRegistrationQuestion":{"type":"object","required":["key","label","hint","control","options"],"additionalProperties":false,"description":"A question the camp's current registration form requires that the registration has not answered.","properties":{"key":{"type":"string","description":"Where the answer is stored, \"<section>.<field>\": a canonical attendee field (child, guardian, secondaryGuardian or emergencyContact) or answers.<question key> for the camp's own question. Send the answer under this key."},"label":{"type":"string","description":"The question as the form asks it."},"hint":{"type":"string","description":"The form's help text; empty when none."},"control":{"enum":["text","textarea","email","phone","date","number","choice","multi-choice","yes-no"],"description":"How the form asks it. date is YYYY-MM-DD; choice takes one of options; multi-choice a list of them; yes-no is \"Yes\" or \"No\"; text and the others are text (up to 200 characters, textarea 2000)."},"options":{"type":"array","items":{"type":"string"},"description":"The offered choices for choice and multi-choice; empty otherwise."}}},"FamilyRegistrationQuestions":{"type":"object","required":["registration","formVersion","questions"],"additionalProperties":false,"properties":{"registration":{"type":"object","required":["id","organizationId","organizationName","confirmationCode","childProfileId","status"],"additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"organizationName":{"type":"string"},"confirmationCode":{"type":"string"},"childProfileId":{"type":["string","null"],"format":"uuid","description":"The saved child profile the registration was made from; null after that profile was removed."},"status":{"enum":["enrolled","waitlisted"],"description":"A cancelled registration is not available here."}}},"formVersion":{"type":"string","description":"The camp's form as read; send it back with the answers. Empty when the camp has no form (and so no questions)."},"questions":{"type":"array","items":{"$ref":"#/components/schemas/FamilyRegistrationQuestion"},"description":"The questions still missing, in form order; empty when the registration answers everything the form requires."}}},"MapScoreBandCounts":{"description":"Counts of records by their current persisted score band. Unavailable includes missing, stale-version, and undisclosed scores.","type":"object","properties":{"strong":{"type":"integer","minimum":0},"good":{"type":"integer","minimum":0},"fair":{"type":"integer","minimum":0},"weak":{"type":"integer","minimum":0},"unavailable":{"type":"integer","minimum":0}},"required":["strong","good","fair","weak","unavailable"]},"BookingProfileCandidate":{"type":"object","additionalProperties":false,"properties":{"childId":{"type":"string","format":"uuid"},"childVersion":{"type":"integer","minimum":1},"firstName":{"type":"string"},"lastName":{"type":"string"},"bookingOptions":{"type":"array","items":{"$ref":"#/components/schemas/BookingProfileOption"}},"guardians":{"type":"array","description":"Eligible guardians, owner first. Returned only to staff with the named permission.","items":{"$ref":"#/components/schemas/BookingProfileGuardianChoice"}}},"required":["childId","childVersion","firstName","lastName","bookingOptions","guardians"]},"BookingProfileOption":{"type":"object","additionalProperties":false,"properties":{"authorizationId":{"type":"string","format":"uuid"},"authorizationVersion":{"type":"integer","minimum":1},"guardianId":{"type":"string","format":"uuid"},"guardianName":{"type":"string"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/BookingProfileField"},"uniqueItems":true},"expiresAt":{"type":"string","format":"date-time"},"grantedByStaff":{"type":"boolean","description":"Staff granted it on the family's behalf; otherwise the family granted it."},"completeness":{"$ref":"#/components/schemas/BookingProfileCompleteness"},"coversEnrolment":{"type":"boolean","description":"The authorization consents to enrolment at the provider."}},"required":["authorizationId","authorizationVersion","guardianId","guardianName","fields","expiresAt","grantedByStaff","completeness","coversEnrolment"],"description":"A live authorization for the search's provider. A guardian's complete options are listed first, newest first within each group."},"BookingProfileGrantInput":{"type":"object","additionalProperties":false,"properties":{"childId":{"type":"string","format":"uuid"},"childVersion":{"type":"integer","minimum":1},"guardianId":{"type":"string","format":"uuid"},"providerOrigin":{"type":"string"},"fields":{"type":"array","minItems":1,"maxItems":15,"uniqueItems":true,"items":{"$ref":"#/components/schemas/BookingProfileField"}},"expiresAt":{"type":"string","format":"date-time"},"enrolment":{"type":"boolean","description":"Consent to enrol the child at the provider, not only share details. Omitted or false shares details only; such an authorization is incomplete."}},"required":["childId","childVersion","guardianId","providerOrigin","fields","expiresAt"]},"BookingProfilePreview":{"type":"object","additionalProperties":false,"properties":{"selection":{"$ref":"#/components/schemas/BookingProfileSelection"},"values":{"$ref":"#/components/schemas/BookingProfileValues"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/BookingProfileField"}},"executionSupport":{"$ref":"#/components/schemas/BookingProfileExecutionSupport"},"completeness":{"$ref":"#/components/schemas/BookingProfileCompleteness"}},"required":["selection","values","fields","executionSupport","completeness"]},"BookingProfileReceipt":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"state":{"type":"string","enum":["queued","dispatched","reconciled","erased"]},"createdAt":{"type":"string","format":"date-time"},"eraseAfter":{"type":"string","format":"date-time"}},"required":["id","state","createdAt","eraseAfter"]},"BookingProfileSelection":{"type":"object","additionalProperties":false,"properties":{"authorizationId":{"type":"string","format":"uuid"},"authorizationVersion":{"type":"integer","minimum":1},"childId":{"type":"string","format":"uuid"},"childVersion":{"type":"integer","minimum":1},"guardianId":{"type":"string","format":"uuid"},"guardianRevision":{"type":"string","pattern":"^[0-9a-f]{64}$"},"providerOrigin":{"type":"string","format":"uri"}},"required":["authorizationId","authorizationVersion","childId","childVersion","guardianId","guardianRevision","providerOrigin"]},"HallPassCampBookingProfileInput":{"type":"object","additionalProperties":false,"required":["sessionId","profileSelection"],"description":"A reviewed authorized family selection. The server resolves current permitted fields, retains real child identity, and stores minimized resolved details in the ordinary run task. Requires explicit CUA without fallback: either a zero-spend checkout review rehearsal, or a purchase with a positive USD maxSpend up to the staff cap, paid with the Hallpass platform card for any provider total at or under maxSpend without a total confirmation; a purchase also requires a complete authorization (every booking field and consent to enrol), returning 409 otherwise. Automatic decisions, site accounts, saved cards and saved answers are refused in both modes. Repeated identical POST recovers the original run without resubmitting it.","properties":{"sessionId":{"type":"string","format":"uuid"},"profileSelection":{"$ref":"#/components/schemas/BookingProfileSelection"},"allowWaitlist":{"type":"boolean","default":false}}},"BookingProfileReadiness":{"type":"object","additionalProperties":false,"description":"Current API route admission for the signed-in staff member. Availability does not authorize a selected profile or establish deployed Pilgrim/CUA confirmation, retention, provider or end-to-end qualification. Each operation rechecks authority. Discovery and preview currently share the booking feature gate.","properties":{"featureEnabled":{"type":"boolean"},"staffPermissionActive":{"type":"boolean"},"discoveryAvailable":{"type":"boolean"},"previewAvailable":{"type":"boolean"},"bookingAdmissionAvailable":{"type":"boolean"},"runModes":{"type":"array","description":"Run modes a saved-profile camp booking is admitted in now, so a client offers only these: empty while booking admission is unavailable, rehearsal (zero-spend CUA checkout review) when it is available, and production (CUA purchase with the platform card up to maxSpend, requiring a complete authorization) while the staff spending cap is above zero.","items":{"type":"string","enum":["rehearsal","production"]}},"blockedReasons":{"type":"array","items":{"type":"string","enum":["feature_disabled","staff_permission_required"]}}},"required":["featureEnabled","staffPermissionActive","discoveryAvailable","previewAvailable","bookingAdmissionAvailable","runModes","blockedReasons"]},"CampRegistrationCapability":{"type":"object","required":["profileVersion","actionVersion","fieldsByRole"],"properties":{"profileVersion":{"type":"string","enum":["1"]},"actionVersion":{"type":"string","enum":["confirmation-v1"]},"fieldsByRole":{"type":"object","additionalProperties":false,"required":["attendee","guardian"],"properties":{"attendee":{"type":"array","uniqueItems":true,"items":{"type":"string","enum":["fullName","givenName","familyName","birthDate","grade","gradeSchoolYear","allergies"]}},"guardian":{"type":"array","uniqueItems":true,"items":{"type":"string","enum":["fullName","givenName","familyName","emailAddress","phoneNumber"]}}}}},"description":"Canonical person fields admitted by the API for explicit zero-spend Pilgrim/CUA checkout review without fallback or automatic decisions. Required native versions are checked at dispatch; this is not runtime health or provider qualification. Values remain in the existing attendee/guardian inputs, never arbitrary answer rows."},"BookingProfileGuardianChoice":{"type":"object","additionalProperties":false,"description":"An adult staff may grant for: the child's owner or a live co-parent assigned to the child. Independent of provider.","properties":{"guardianId":{"type":"string","format":"uuid"},"guardianName":{"type":"string"}},"required":["guardianId","guardianName"]},"BookingProfileStaffAuthorization":{"type":"object","additionalProperties":false,"properties":{"authorizationId":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1},"childId":{"type":"string","format":"uuid"},"guardianId":{"type":"string","format":"uuid"},"providerOrigin":{"type":"string","description":"Exact HTTPS origin, normalized to scheme and lowercase host."},"expiresAt":{"type":"string","format":"date-time"},"executionSupport":{"$ref":"#/components/schemas/BookingProfileExecutionSupport"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/BookingProfileField"},"minItems":4,"uniqueItems":true},"completeness":{"$ref":"#/components/schemas/BookingProfileCompleteness"}},"required":["authorizationId","version","childId","guardianId","providerOrigin","expiresAt","executionSupport","fields","completeness"]},"BookingProfileViewerAuthorization":{"type":"object","additionalProperties":false,"properties":{"authorizationId":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1},"childId":{"type":"string","format":"uuid"},"guardianId":{"type":"string","format":"uuid"},"guardianName":{"type":"string"},"providerOrigin":{"type":"string"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/BookingProfileField"}},"grantedAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"grantedByStaff":{"type":"boolean","description":"True when School's Out staff granted on the family's behalf."},"coversEnrolment":{"type":"boolean"}},"required":["authorizationId","version","childId","guardianId","guardianName","providerOrigin","fields","grantedAt","expiresAt","grantedByStaff","coversEnrolment"]},"BookingProfileExecutionSupport":{"type":"object","additionalProperties":false,"description":"API private-transport support only; not deployed executor readiness, provider acceptance or authority. Preview evaluates populated approved fields; grants evaluate the requested permission scope. Unsupported fields require an explicitly reviewed different grant or a qualified transport upgrade.","properties":{"contractVersion":{"type":"string"},"supported":{"type":"boolean"},"unsupportedFields":{"type":"array","items":{"$ref":"#/components/schemas/BookingProfileField"}}},"required":["contractVersion","supported","unsupportedFields"]},"OverseerCampaignFields":{"type":"object","additionalProperties":false,"properties":{"organization":{"type":"array","items":{"type":"string","maxLength":8000}},"activity":{"type":"array","items":{"type":"string","maxLength":8000}},"session":{"type":"array","items":{"type":"string","maxLength":8000}}},"required":["organization","activity","session"]},"OverseerCampaignRegion":{"type":"object","additionalProperties":false,"properties":{"datasetId":{"type":"string","maxLength":8000},"kind":{"type":"string","enum":["state","metro","county","city","neighborhood","zip"]},"code":{"type":"string","maxLength":8000}},"required":["datasetId","kind","code"]},"OverseerCampaignResolvedRegion":{"type":"object","additionalProperties":false,"properties":{"datasetId":{"type":"string","maxLength":8000},"kind":{"type":"string","enum":["state","metro","county","city","neighborhood","zip"]},"code":{"type":"string","maxLength":8000},"name":{"type":"string","maxLength":8000},"source":{"type":"string","maxLength":8000},"sourceUrl":{"type":"string","maxLength":8000},"vintage":{"type":"string","maxLength":8000},"placeType":{"type":"string","maxLength":8000}},"required":["datasetId","kind","code","name","source","sourceUrl","vintage"]},"OverseerCampaignRegionPage":{"type":"object","additionalProperties":false,"properties":{"regions":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignResolvedRegion"}},"datasetId":{"anyOf":[{"type":"string","maxLength":8000},{"type":"null"}]},"datasetVersion":{"anyOf":[{"type":"string","maxLength":8000},{"type":"null"}]},"available":{"type":"boolean"},"limit":{"type":"integer","minimum":0},"offset":{"type":"integer","minimum":0},"hasMore":{"type":"boolean"}},"required":["regions","datasetId","datasetVersion","available","limit","offset","hasMore"]},"OverseerCampaignFieldCapability":{"type":"object","additionalProperties":false,"properties":{"key":{"type":"string","maxLength":8000},"label":{"type":"string","maxLength":8000},"group":{"type":"string","maxLength":8000},"description":{"type":"string","maxLength":8000},"canonicalField":{"type":"string","maxLength":8000},"inheritanceUnit":{"type":"string","maxLength":8000}},"required":["key","label","group","description","canonicalField"]},"OverseerCampaignOptions":{"type":"object","additionalProperties":false,"properties":{"workTypes":{"type":"array","items":{"type":"string","enum":["discover_organizations","discover_offerings","validate","remove_nonqualifying"]}},"listingKinds":{"type":"array","items":{"type":"string","enum":["organization","activity","session"]}},"fields":{"type":"object","additionalProperties":false,"properties":{"organization":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignFieldCapability"}},"activity":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignFieldCapability"}},"session":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignFieldCapability"}}},"required":["organization","activity","session"]}},"required":["workTypes","listingKinds","fields"]},"OverseerCampaignBrief":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","description":"Campaign name, at most 200 UTF-8 bytes. Blank or whitespace-only names are assigned a readable name from the selected work and listing types during preview and launch."},"workTypes":{"type":"array","uniqueItems":true,"items":{"type":"string","enum":["discover_organizations","discover_offerings","validate","remove_nonqualifying"]},"description":"May be empty when context provides freeform instructions. Discovery of offerings operates under existing organizations."},"listingKinds":{"type":"array","minItems":1,"uniqueItems":true,"items":{"type":"string","enum":["organization","activity","session"]}},"fields":{"$ref":"#/components/schemas/OverseerCampaignFields"},"geographicRegion":{"anyOf":[{"$ref":"#/components/schemas/OverseerCampaignRegion"},{"type":"null"}]},"validationAgeDays":{"type":"number","minimum":0,"description":"Literal elapsed days since field validation; zero disables the age filter. This value is not a freshness score half-life."},"leaveMissingFieldsEmpty":{"type":"boolean","default":false,"description":"Validation only: skip selected fields without a populated local canonical value. Inherited values do not create child work. False and zero remain populated; null, blank text, empty collections and ranges without endpoints do not. Media uses this listing's active owned images, including an organization's own logos. Omitted is false."},"context":{"type":"string","description":"Freeform research instructions, at most 65536 UTF-8 bytes."},"endCriteria":{"type":"string","description":"Observable completion criteria, at most 16384 UTF-8 bytes."},"activeWorkLimitSeconds":{"anyOf":[{"type":"integer","minimum":1,"description":"Maximum active work seconds. Null means no limit; paused and idle time do not count."},{"type":"null"}]},"recordTargets":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignRecordTarget"},"maxItems":100,"description":"Optional exact catalog targets. The server requires visible existing records of selected kinds and resolves only this scope and explicit discovery context parents."}},"required":["name","workTypes","listingKinds","fields","geographicRegion","validationAgeDays","context","endCriteria","activeWorkLimitSeconds"]},"OverseerCampaignMember":{"type":"object","additionalProperties":false,"properties":{"configurationId":{"type":"string","description":"Existing configuration UUID when one is installed; empty for legacy records without a configuration."},"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","description":"Linked organization UUID, or empty for an existing unlinked activity or session. Unlinked listings remain researchable; unsupported corrections and removals must be recorded as blocked.","pattern":"^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})?$"},"name":{"type":"string","maxLength":8000},"workTypes":{"type":"array","items":{"type":"string","enum":["discover_organizations","discover_offerings","validate","remove_nonqualifying"]}},"fields":{"type":"array","items":{"type":"string","maxLength":8000}},"context":{"type":"boolean"}},"required":["configurationId","kind","id","organizationId","name","workTypes","fields","context"]},"OverseerCampaignChecklistItem":{"type":"object","additionalProperties":false,"properties":{"configurationId":{"type":"string","description":"Existing configuration UUID when one is installed; empty for legacy records without a configuration."},"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","description":"Linked organization UUID, or empty for an existing unlinked activity or session. Unlinked listings remain researchable; unsupported corrections and removals must be recorded as blocked.","pattern":"^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})?$"},"name":{"type":"string","maxLength":8000},"workTypes":{"type":"array","items":{"type":"string","enum":["discover_organizations","discover_offerings","validate","remove_nonqualifying"]}},"fields":{"type":"array","items":{"type":"string","maxLength":8000}},"context":{"type":"boolean"},"outcome":{"type":"string","maxLength":8000},"summary":{"type":"string","maxLength":8000},"research":{"$ref":"#/components/schemas/OverseerCampaignResearch","description":"Structured unresolved findings and any staff action required. Omitted on legacy checklist rows without structured research."},"activeWorkSeconds":{"type":"integer","minimum":0},"updatedAt":{"type":"string","format":"date-time"}},"required":["configurationId","kind","id","organizationId","name","workTypes","fields","context","outcome","summary","activeWorkSeconds","updatedAt"]},"OverseerCampaignResearch":{"type":"object","additionalProperties":false,"description":"Saved structured research for a checklist listing. Findings explain unresolved field facts; a blocker records a specific staff question or action needed to continue.","properties":{"findings":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignFieldFinding"}},"blocker":{"$ref":"#/components/schemas/OverseerCampaignBlocker","description":"Required when the listing outcome is blocked; absent when staff action is not required."}},"required":["findings"]},"OverseerCampaignFieldFinding":{"type":"object","additionalProperties":false,"description":"An unresolved research fact. `fields` must name selected fields except for outside_scope, which may use an empty list. Findings do not block the listing or establish accepted freshness for their fields.","properties":{"fields":{"type":"array","items":{"type":"string","maxLength":8000}},"reason":{"type":"string","enum":["conflicting_sources","source_unavailable","outside_scope"]},"summary":{"type":"string","minLength":1,"maxLength":8000}},"required":["fields","reason","summary"]},"OverseerCampaignBlocker":{"type":"object","additionalProperties":false,"description":"A concrete staff decision or action required to continue a blocked listing.","properties":{"reason":{"type":"string","enum":["identity","ownership","staff_decision"]},"question":{"type":"string","minLength":1,"maxLength":8000}},"required":["reason","question"]},"OverseerCampaignChecklistPage":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignChecklistItem"}},"nextAfterId":{"type":"string","maxLength":8000},"hasMore":{"type":"boolean"}},"required":["items","nextAfterId","hasMore"]},"OverseerCampaignProgressCounts":{"type":"object","additionalProperties":false,"properties":{"total":{"type":"integer","minimum":0},"checked":{"type":"integer","minimum":0}},"required":["total","checked"]},"OverseerCampaignProgress":{"type":"object","additionalProperties":false,"properties":{"total":{"type":"integer","minimum":0},"checked":{"type":"integer","minimum":0},"unchanged":{"type":"integer","minimum":0},"proposals":{"type":"integer","minimum":0},"blocked":{"type":"integer","minimum":0},"researching":{"type":"integer","minimum":0},"pending":{"type":"integer","minimum":0},"percent":{"anyOf":[{"type":"number","minimum":0,"maximum":100},{"type":"null"}]},"byKind":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/OverseerCampaignProgressCounts"}},"discoveryOpen":{"type":"boolean"}},"required":["total","checked","unchanged","proposals","blocked","researching","pending","percent","byKind","discoveryOpen"]},"OverseerCampaignEstimate":{"type":"object","additionalProperties":false,"properties":{"minSeconds":{"type":"integer","minimum":0},"maxSeconds":{"type":"integer","minimum":0},"basis":{"type":"string","maxLength":8000},"discoveryOpen":{"type":"boolean"}},"required":["minSeconds","maxSeconds","basis","discoveryOpen"]},"OverseerCampaignReport":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"round":{"type":"integer","minimum":0},"kind":{"type":"string","maxLength":8000},"summary":{"type":"string","maxLength":8000},"details":{},"createdAt":{"type":"string","format":"date-time"}},"required":["id","round","kind","summary","details","createdAt"]},"OverseerCampaignRuntime":{"additionalProperties":false,"properties":{"actorId":{"description":"User who last saved the current directive or cleared its memory, or empty before the first save.","type":"string"},"createdAt":{"format":"date-time","type":"string"},"directive":{"type":"string","description":"Compatibility projection containing only the enabled standing instruction text. Use directives when editing."},"executionActive":{"description":"True only when the current revision is running and its worker lease has not expired at response time. A saved but unclaimed directive, absent/expired lease, and execution belonging to a superseded revision return false. Does not establish process health or worker configuration. Always false while paused, including while a revoked native process is finishing cleanup.","type":"boolean"},"leaseExpiresAt":{"description":"Expiry of the current revision worker lease, or null if not claimed. May be in the past; executionActive is then false. Contains no authority token.","format":"date-time","type":["string","null"]},"nextReportAt":{"format":"date-time","type":["string","null"]},"nextWakeAt":{"format":"date-time","type":["string","null"]},"reportCadenceSeconds":{"description":"Worker-interpreted report cadence. Zero disables scheduled reports.","minimum":0,"type":"integer"},"revision":{"minimum":0,"type":"integer"},"state":{"enum":["idle","running","waiting","needs_rewording","satisfied","blocked"],"type":"string"},"subagentCount":{"description":"Last-reported pending or running direct subagents in the current execution. Reports can lag actual activity and exclude nested children. Null while execution is active without valid telemetry; zero when executionActive is false. This is not an instantaneous total or cumulative spawned count.","minimum":0,"type":["integer","null"]},"subagentObservedAt":{"description":"Time the worker recorded the latest valid direct-subagent aggregate. Null when its count is unavailable or executionActive is false. A fresh timestamp does not establish that every child reported a new state.","format":"date-time","type":["string","null"]},"summary":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"directives":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/OverseerDirective"}},"disabledTools":{"type":"array","uniqueItems":true,"items":{"type":"string"},"description":"Disabled names from availableTools. Mandatory history, review reconciliation and lifecycle tools cannot be disabled."},"availableTools":{"type":"array","items":{"$ref":"#/components/schemas/OverseerToolOption"}},"pendingMessages":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/OverseerMessage"},"description":"Undelivered one-off requests for the current revision in submission order. A message being processed remains queued until its turn finishes successfully."},"paused":{"type":"boolean","description":"Global operator pause, independent of instruction revision and underlying work state. Prevents new agent turns and revokes current turn authority without clearing memory, settings or queued requests."},"pauseVersion":{"type":"integer","minimum":0,"description":"Monotonic version for explicit pause/resume changes. Compare with expectedPauseVersion; unchanged desired state with matching versions is a no-op."}},"required":["revision","directive","state","actorId","summary","nextWakeAt","reportCadenceSeconds","nextReportAt","createdAt","updatedAt","executionActive","leaseExpiresAt","subagentCount","subagentObservedAt","directives","disabledTools","availableTools","pendingMessages","paused","pauseVersion"],"type":"object"},"OverseerCampaign":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":8000},"ownerId":{"type":"string","format":"uuid"},"revision":{"type":"integer","minimum":0},"briefVersion":{"type":"integer","minimum":0},"round":{"type":"integer","minimum":0},"state":{"type":"string","enum":["queued","researching","needs_input","ready_for_review","merge_preparing","merging","completed","cancelled"]},"paused":{"type":"boolean"},"pauseVersion":{"type":"integer","minimum":0},"globalPaused":{"type":"boolean"},"brief":{"$ref":"#/components/schemas/OverseerCampaignBrief"},"summary":{"type":"string","maxLength":8000},"progress":{"$ref":"#/components/schemas/OverseerCampaignProgress"},"estimate":{"anyOf":[{"$ref":"#/components/schemas/OverseerCampaignEstimate"},{"type":"null"}]},"activeWorkSeconds":{"type":"integer","minimum":0},"activeWorkLimitSeconds":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}]},"runtime":{"$ref":"#/components/schemas/OverseerCampaignRuntime"},"checklist":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignChecklistItem"}},"reports":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignReport"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"requestId":{"type":"string","format":"uuid"}},"required":["id","name","ownerId","revision","briefVersion","round","state","paused","pauseVersion","globalPaused","brief","summary","progress","estimate","activeWorkSeconds","activeWorkLimitSeconds","runtime","reports","createdAt","updatedAt","requestId"]},"OverseerCampaignPage":{"type":"object","additionalProperties":false,"properties":{"campaigns":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaign"}},"nextBefore":{"type":"string","maxLength":8000},"hasMore":{"type":"boolean"}},"required":["campaigns","nextBefore","hasMore"]},"OverseerCampaignPreview":{"type":"object","additionalProperties":false,"properties":{"brief":{"$ref":"#/components/schemas/OverseerCampaignBrief"},"progress":{"$ref":"#/components/schemas/OverseerCampaignProgress"},"estimate":{"anyOf":[{"$ref":"#/components/schemas/OverseerCampaignEstimate"},{"type":"null"}]}},"required":["brief","progress","estimate"]},"OverseerCampaignProposalVersion":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1}},"required":["id","version"]},"OverseerCampaignProposalPayload":{"type":"object","additionalProperties":false,"properties":{"activityId":{"format":"uuid","type":"string"},"checkedNotFound":{"type":"array","items":{"type":"string"},"description":"Fields explicitly researched without a usable value. Empty research results use this same conservative state; existing canonical values are preserved. Omitted on historical reviews without recorded checks."},"cleared":{"items":{"type":"string"},"type":"array"},"entityId":{"format":"uuid","type":"string"},"evidence":{"items":{"$ref":"#/components/schemas/OverseerReviewEvidence"},"type":"array"},"expected":{"type":"object"},"media":{"$ref":"#/components/schemas/OverseerMediaContribution","description":"Submitted media contribution; the applied result records its actual effect."},"expectedMedia":{"$ref":"#/components/schemas/OverseerMediaProjection","description":"The entity's media when the proposal was made. Omitted when the proposal changes no media."},"mediaInspections":{"type":"array","items":{"$ref":"#/components/schemas/OverseerMediaInspection"},"description":"Omitted when no source image was inspected."},"gapId":{"type":"string","description":"The scraper gap this update addresses; retained in its contribution origin."},"fields":{"type":"object"},"kind":{"enum":["organization","activity","session"]},"operation":{"enum":["create","update","remove"]},"organizationId":{"format":"uuid","type":"string"},"removeRelevantUrls":{"items":{"type":"string"},"type":"array"},"removal":{"$ref":"#/components/schemas/OverseerReviewRemoval","description":"Present only for operation=remove. Saving a proposal leaves canonical records live until explicit staff approval."},"survivorTarget":{"$ref":"#/components/schemas/OverseerRemovalEntity"},"comparisonTargets":{"type":"array","items":{"$ref":"#/components/schemas/OverseerRemovalEntity"}},"reviewId":{"type":"string","format":"uuid"},"expectedVersion":{"type":"integer","minimum":1},"expectedFeedbackId":{"type":"integer","minimum":0}},"required":["kind","organizationId","fields","cleared","removeRelevantUrls","expected","evidence"]},"OverseerCampaignProposal":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1},"campaignId":{"type":"string","format":"uuid"},"round":{"type":"integer","minimum":1},"kind":{"type":"string","enum":["organization","activity","session"]},"operation":{"type":"string","enum":["create","update","remove"]},"label":{"type":"string","maxLength":8000},"decision":{"type":"string","enum":["included","excluded","revision_requested"]},"feedback":{"type":"string","maxLength":8000},"dependencies":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignProposalVersion"}},"provisionalEntityId":{"type":"string","format":"uuid"},"review":{"$ref":"#/components/schemas/OverseerReview"}},"required":["id","version","campaignId","round","kind","operation","label","decision","feedback","dependencies","review"]},"OverseerCampaignResultCounts":{"type":"object","additionalProperties":false,"properties":{"included":{"type":"integer","minimum":0},"excluded":{"type":"integer","minimum":0},"revisionRequested":{"type":"integer","minimum":0},"create":{"type":"integer","minimum":0},"update":{"type":"integer","minimum":0},"remove":{"type":"integer","minimum":0}},"required":["included","excluded","revisionRequested","create","update","remove"]},"OverseerCampaignResultsPage":{"type":"object","additionalProperties":false,"properties":{"proposals":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignProposal"}},"total":{"type":"integer","minimum":0},"offset":{"type":"integer","minimum":0},"limit":{"type":"integer","minimum":0},"selectionRevision":{"type":"integer","minimum":0},"counts":{"$ref":"#/components/schemas/OverseerCampaignResultCounts"},"round":{"type":"integer","minimum":1}},"required":["proposals","total","offset","limit","selectionRevision","counts","round"]},"OverseerCampaignResultsQuery":{"type":"object","additionalProperties":false,"properties":{"q":{"type":"string","maxLength":500},"kind":{"type":"string","enum":["organization","activity","session"]},"operation":{"type":"string","enum":["create","update","remove"]},"decision":{"type":"string","enum":["included","excluded","revision_requested"]},"outcome":{"type":"string","enum":["pending","researching","unchanged","proposals","blocked","excluded"],"description":"Research outcome of the campaign checklist entry. Proposal-only candidates have no outcome."},"hasProposals":{"type":"boolean","description":"Require at least one proposal, independent of research outcome. Filter-based selections always resolve proposals."}},"required":[]},"OverseerCampaignResultSelection":{"type":"object","additionalProperties":false,"properties":{"versions":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignProposalVersion"},"maxItems":10000},"query":{"$ref":"#/components/schemas/OverseerCampaignResultsQuery"},"exceptIds":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":10000}},"required":[],"oneOf":[{"required":["versions"],"not":{"required":["query"]}},{"required":["query"],"not":{"required":["versions"]}}]},"OverseerCampaignDecisionResult":{"type":"object","additionalProperties":false,"properties":{"requestId":{"type":"string","format":"uuid"},"selectionRevision":{"type":"integer","minimum":0},"round":{"type":"integer","minimum":1},"changed":{"type":"integer","minimum":0},"versions":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignProposalVersion"}}},"required":["requestId","selectionRevision","round","changed","versions"]},"OverseerCampaignMergeItem":{"type":"object","additionalProperties":false,"properties":{"proposalId":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1},"payload":{"$ref":"#/components/schemas/OverseerCampaignProposalPayload"},"conflict":{"type":"string","maxLength":8000},"label":{"type":"string","description":"Immutable display label from the exact staged proposal version captured when this manifest was prepared. Older retained manifests may return an empty label."}},"required":["proposalId","version","payload","label"]},"OverseerCampaignMergeManifest":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"campaignId":{"type":"string","format":"uuid"},"round":{"type":"integer","minimum":1},"selectionRevision":{"type":"integer","minimum":0},"proposals":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignMergeItem"}},"createdAt":{"type":"string","format":"date-time"},"requestId":{"type":"string","format":"uuid"}},"required":["id","campaignId","round","selectionRevision","proposals","createdAt","requestId"]},"OverseerCampaignMergeResult":{"type":"object","additionalProperties":false,"properties":{"manifestId":{"type":"string","format":"uuid"},"campaign":{"$ref":"#/components/schemas/OverseerCampaign"},"reviews":{"type":"array","items":{"$ref":"#/components/schemas/OverseerReview"}},"result":{},"requestId":{"type":"string","format":"uuid"}},"required":["manifestId","campaign","reviews","requestId"]},"OverseerCampaignFeedbackNote":{"type":"object","additionalProperties":false,"properties":{"proposalId":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1},"feedback":{"type":"string","minLength":1}},"required":["proposalId","version","feedback"]},"OverseerCampaignProposalDetail":{"type":"object","additionalProperties":false,"properties":{"proposal":{"$ref":"#/components/schemas/OverseerCampaignProposal"},"detail":{"$ref":"#/components/schemas/OverseerReviewDetail"}},"required":["proposal","detail"]},"OverseerCampaignFeedbackBatch":{"type":"object","additionalProperties":false,"properties":{"selection":{"$ref":"#/components/schemas/OverseerCampaignResultSelection"},"feedback":{"type":"string","minLength":1,"maxLength":8000}},"required":["selection","feedback"]},"OverseerCampaignMergeRejection":{"type":"object","additionalProperties":false,"properties":{"success":{"const":false},"message":{"type":"string","maxLength":8000},"error":{"const":true},"requestId":{"type":"string","format":"uuid"},"manifestId":{"type":"string","format":"uuid"},"mergeOutcome":{"const":"rejected"}},"required":["success","message","requestId","manifestId","mergeOutcome"]},"OverseerCampaignRecordTarget":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"}},"required":["kind","id"]},"OverseerCampaignLaunchRejection":{"type":"object","additionalProperties":false,"properties":{"success":{"const":false},"message":{"type":"string"},"error":{"const":true},"requestId":{"type":"string","format":"uuid"},"campaignOutcome":{"const":"rejected"}},"required":["success","message","requestId","campaignOutcome"]},"OverseerCampaignCommandRejection":{"type":"object","additionalProperties":false,"properties":{"success":{"const":false},"message":{"type":"string"},"error":{"const":true},"requestId":{"type":"string","format":"uuid"},"operation":{"type":"string","enum":["decision","feedback","prepare"]},"commandOutcome":{"const":"rejected"}},"required":["success","message","requestId","operation","commandOutcome"]},"OverseerCampaignListingProposal":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1},"round":{"type":"integer","minimum":1},"operation":{"type":"string","enum":["create","update","remove"]},"label":{"type":"string"},"decision":{"type":"string","enum":["included","excluded","revision_requested"]},"feedback":{"type":"string"},"dependencies":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignProposalVersion"}},"createdAt":{"type":"string","format":"date-time"}},"required":["id","version","round","operation","label","decision","feedback","dependencies","createdAt"]},"OverseerCampaignListing":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"organizationId":{"type":"string"},"organizationName":{"type":"string"},"parentKind":{"type":"string","enum":["","organization","activity"]},"parentId":{"type":"string"},"parentName":{"type":"string"},"configurationId":{"type":"string"},"context":{"type":"boolean"},"discovery":{"type":"boolean"},"checklist":{"type":"boolean"},"outcome":{"type":"string","enum":["","pending","researching","unchanged","proposals","blocked","excluded"]},"summary":{"type":"string"},"research":{"$ref":"#/components/schemas/OverseerCampaignResearch","description":"Structured research saved for this checklist listing. Omitted for proposal-only candidates and legacy checklist rows without structured research."},"workTypes":{"type":"array","items":{"type":"string"}},"fields":{"type":"array","items":{"type":"string"}},"proposals":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignListingProposal"}},"parentProposal":{"$ref":"#/components/schemas/OverseerCampaignProposalVersion","description":"Explicit current proposal reference for a provisional parent activity. Omitted for canonical or unavailable parents; do not infer this reference from unrelated proposal dependencies."}},"required":["kind","id","name","organizationId","organizationName","parentKind","parentId","parentName","configurationId","context","discovery","checklist","outcome","summary","workTypes","fields","proposals"]},"OverseerCampaignListingsPage":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignListing"}},"total":{"type":"integer"},"proposalTotal":{"type":"integer"},"hasMore":{"type":"boolean"},"nextAfter":{"type":"string"},"round":{"type":"integer","minimum":1},"selectionRevision":{"type":"integer","minimum":0},"counts":{"$ref":"#/components/schemas/OverseerCampaignResultCounts"}},"required":["items","total","proposalTotal","hasMore","nextAfter","round","selectionRevision","counts"]},"OverseerCampaignListingFeedbackNote":{"type":"object","additionalProperties":false,"properties":{"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"},"feedback":{"type":"string","minLength":1,"maxLength":8000}},"required":["kind","id","feedback"]},"BookingProfileCompleteField":{"type":"string","description":"The complete authorization scope: exactly the profile fields the private executor can fill. Staff grants always share these. A narrower family scope still previews and starts runs; BookingProfileCompleteness reports what it leaves out.","enum":["child.firstName","child.lastName","child.birthDate","child.grade","child.gradeSchoolYear","child.allergies","guardian.fullName","guardian.givenName","guardian.familyName","guardian.email","guardian.phone"]},"BookingProfileCompleteness":{"type":"object","additionalProperties":false,"description":"Whether an authorization is complete: its field scope is the complete scope (BookingProfileCompleteField) and it consents to enrolment. missingFields are complete-scope fields it does not share; unsupportedFields are shared fields the executor cannot fill. Judges the scope only, not whether the profile holds a value.","properties":{"complete":{"type":"boolean"},"missingFields":{"type":"array","items":{"$ref":"#/components/schemas/BookingProfileField"}},"unsupportedFields":{"type":"array","items":{"$ref":"#/components/schemas/BookingProfileField"}},"enrolment":{"type":"boolean","description":"The authorization consents to enrolling the child at the provider, not only sharing details. A complete authorization has it."}},"required":["complete","missingFields","unsupportedFields","enrolment"]},"HallPassRunPaymentRecord":{"type":"object","additionalProperties":false,"description":"What a paid saved-profile run keeps after retention erases its family details: only values the receipt contract validates (amount, the charged status, confirmation time, card kind). Merchant, provider and card references, card display text, line items and evidence are free provider text and are never kept.","properties":{"amount":{"$ref":"#/components/schemas/HallPassRunMoney"},"paymentStatus":{"type":"string"},"confirmedAt":{"type":"string","format":"date-time"},"cardKind":{"type":"string"}},"required":["amount","paymentStatus","confirmedAt","cardKind"]},"MarketSupplyResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","required":["tier","brands","sessionSites","liveBrandsByRegion","liveCategoryCounts","discoveryByMonth","brandsTruncated","sessionSitesTruncated","warnings"],"properties":{"tier":{"type":"string","description":"Effective catalog tier, or preview."},"brands":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Projected camps: id, name, city, region, coordinates, categories, age_range_years, financial_assistance_availability, external_ids, canonical_url, created_at."},"sessionSites":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Program locations grouped by organization and venue, with session/activity counts, session price bounds, dates, and care availability."},"liveBrandsByRegion":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"region and organizations count."},"liveCategoryCounts":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"category and organizations count."},"discoveryByMonth":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"month (YYYY-MM) and organizations count."},"brandsTruncated":{"type":"boolean"},"sessionSitesTruncated":{"type":"boolean"},"warnings":{"type":"array","items":{"type":"string"},"description":"Independent sections that could not be read; failed sections are empty, not authoritative zero populations."}}}}},"MarketListedCountsResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","required":["tier","rows"],"properties":{"tier":{"type":"string","description":"Effective catalog tier, or preview."},"rows":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"region, city, and organizations count."}}}}},"RevenueSupplyResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","required":["groups","daySessions","groupsTruncated","daySessionsTruncated","groupLimit","daySessionLimit"],"properties":{"groups":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Non-daily session groups: region, city, prices, status, ended, full, week_units, sessions, sessions_with_capacity, seats."},"daySessions":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Daily-priced sessions: region, city, prices, status, ended, full, week_units, capacity, schedule_configurations."},"groupsTruncated":{"type":"boolean"},"daySessionsTruncated":{"type":"boolean"},"groupLimit":{"type":"integer","minimum":1},"daySessionLimit":{"type":"integer","minimum":1}}}}},"SessionCalendarResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","required":["tier","rows"],"properties":{"tier":{"type":"string","description":"Effective catalog tier, or preview."},"rows":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Sessions overlapping the requested inclusive date window, ordered by start date and id: id, name, start_date, end_date, start_time, end_time, city, region, activity_name, camp_name."}}}}},"FamilyGroupPlanAnswer":{"type":"object","required":["member","answer"],"properties":{"member":{"$ref":"#/components/schemas/FamilyGroupPerson"},"answer":{"enum":["yes","maybe","no"]}},"additionalProperties":false},"FamilyGroupPlan":{"type":"object","required":["id","groupId","title","note","date","time","activity","createdBy","mine","removable","answers","yourAnswer","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"groupId":{"type":"string","format":"uuid"},"title":{"type":"string","minLength":1,"maxLength":120},"note":{"type":["string","null"],"maxLength":1000},"date":{"type":"string","format":"date","description":"The day of the plan."},"time":{"type":["string","null"],"pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","description":"Local time on date (HH:MM, 24-hour), as entered; null when the plan has no set time."},"activity":{"oneOf":[{"$ref":"#/components/schemas/FamilySocialItemSummary"},{"type":"null"}],"description":"The catalog activity the plan is for; available false once the catalog no longer shows it."},"createdBy":{"oneOf":[{"$ref":"#/components/schemas/FamilyGroupPerson"},{"type":"null"}],"description":"Who proposed the plan; null once their account is deleted."},"mine":{"type":"boolean","description":"You proposed the plan."},"removable":{"type":"boolean","description":"You may delete the plan: you proposed it or you own the group."},"answers":{"type":"array","items":{"$ref":"#/components/schemas/FamilyGroupPlanAnswer"},"description":"Members' answers, oldest first. A member who leaves the group takes their answer with them."},"yourAnswer":{"enum":["yes","maybe","no",null]},"createdAt":{"type":"string","format":"date-time"}},"additionalProperties":false,"description":"A plan the group is making: a day, an optional time and activity, and who's going."},"FamilyGroupShared":{"type":"object","required":["kind","id","title","available","collection","activity","plan"],"properties":{"kind":{"enum":["collection","activity","plan"]},"id":{"type":["string","null"],"format":"uuid","description":"The shared item; null once a shared collection or plan is deleted."},"title":{"type":"string","minLength":1,"maxLength":200,"description":"The item's name when it was shared."},"available":{"type":"boolean","description":"You can open the item now: a collection you own or may open, an activity the catalog shows, a plan the group still has. Otherwise show the title alone."},"collection":{"oneOf":[{"$ref":"#/components/schemas/FamilySocialCollectionSummary"},{"type":"null"}],"description":"The collection card, when kind is collection and available."},"activity":{"oneOf":[{"$ref":"#/components/schemas/FamilySocialItemSummary"},{"type":"null"}],"description":"The activity card, when kind is activity."},"plan":{"oneOf":[{"$ref":"#/components/schemas/FamilyGroupPlan"},{"type":"null"}],"description":"The plan, when kind is plan and available."}},"additionalProperties":false,"description":"What a message shares, shown as a card."}},"securitySchemes":{"AccountCredential":{"description":"Current account credential. HTTP APIs and research tools do not accept SQL statements. Intelligence alternatively uses its signed session cookie; cookie POST requests require the session-bound CSRF token and trusted origin. Family routes accept the account credential only with x-session-id naming a live session of that account.","in":"header","name":"x-api-key","type":"apiKey"},"ApiKeyAuth":{"description":"Account credential or named machine key for integrations. Keep it server-side and send it in x-api-key. Intelligence browser requests separately support the existing signed session cookie; cookie mutations require CSRF verification. Family routes read family data only through a session-backed credential: the cookie naming a live session, or the account credential with x-session-id naming a live session of that account. The catalog-only consumer site key (SCHOOLS_OUT_CATALOG_API_KEY, held by the parent site's server) reads only the catalog list, facets and detail routes through the family projection with no household; public GET routes serve it as an anonymous visitor, every route that needs an account answers it 403, and with x-session-id or x-viewer-id it is 401.","in":"header","name":"x-api-key","type":"apiKey"},"CrawlerCapability":{"type":"http","scheme":"bearer","description":"Short-lived staff capability issued by POST /api/ingestion/crawler-worker/access. Bound to the exact worker ID and browser Origin. Send to that worker origin only; API cookies and x-api-key do not authenticate worker requests."},"CrawlerPublisher":{"type":"http","scheme":"bearer","description":"Dedicated server-only CRAWLER_SYNC_TOKEN. This credential publishes counts and ready URL snapshots; browser Origin requests are rejected."},"PilgrimFleetReporter":{"type":"http","scheme":"bearer","description":"Dedicated manager-only PILGRIM_FLEET_REPORT_TOKEN. Grants only sanitized node-status publication."},"OnboardingKey":{"type":"apiKey","in":"header","name":"x-onboarding-key","description":"A key from ONBOARDING_PUBLIC_KEYS, held by the parent app for its signed-out first-time onboarding. It opens only the /api/onboarding routes; it is not an account, machine, service or site credential, and the identity resolver never reads it."}}},"info":{"description":"Read-only external integration access to the canonical School’s Out catalog and scores. Legacy Insights datasets require ADMIN access. SQL, generic database access, physical table names, and writes are not part of the public API. Product and operator operations are marked x-internal and retain their own credential, plan, role, origin and CSRF requirements.","title":"School’s Out developer API","version":"0.2.0"},"openapi":"3.1.0","paths":{"/api/hallpass/video/{asset}":{"get":{"summary":"Read staff fleet live video","x-internal":true,"x-auth":"Current signed-in staff account acting as itself via identity.Resolver and SessionAccount(), plus staff/ADMIN access. Machine, service and delegated credentials refused. Every asset rechecks authorization.","x-curl-examples":[{"title":"Read the staff fleet playlist","code":"curl --config \"/private/intelligence-staff.curl\" \"https://apis.schoolsoutapp.com/api/hallpass/video/index.m3u8\""}],"description":"Current staff account sessions only. Each manifest and segment is authorized independently. No shared caching. Returns 503 while relay is unavailable.","tags":["Internal Hallpass runs"],"parameters":[{"name":"asset","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"HLS playlist or media segment"},"403":{"description":"Staff account session required"},"503":{"description":"Fleet video unavailable"}}}},"/api/activities/facets":{"get":{"description":"Counts under the canonical list filters and current field access. A single-field request with snapshot=true and no search or filters reads durable incrementally maintained counts after relevant background work settles. While committed edits, deletion, blacklisting, or calendar expiry work is pending, the request falls back to live counts; failed refreshes leave that fallback active. Filtered requests and the default stay live. A parent's own account with a family profile reads facets under the same family filters as the list (snapshot is ignored), with its tier's facets when its tier may read them plus the family facets: category, skill_level, format, seasonality, interest and age_group on activities, and category, city, registration_status, interest and start_week on sessions; interest values are feed interest ids with their labels, counted per offering. The same structured filter JSON and legacy list filters narrow the population using AND before aggregation. Structured filters always use live counts. q searches facet values; search carries the list text search. All filter branches enforce current field disclosure.","parameters":[{"description":"Facet field; required when snapshot=true.","in":"query","name":"field","schema":{"type":"string"}},{"description":"Use the stored unfiltered chart distribution. Must be exactly true or false.","in":"query","name":"snapshot","schema":{"default":false,"type":"boolean"}},{"in":"query","name":"limit","schema":{"default":30,"maximum":100,"minimum":1,"type":"integer"},"description":"With field, how many values to return: a whole number from 1 to 100, given once; any other value answers 400."},{"description":"Filter records before aggregation; uses live counts. Activity matching also includes the linked organization's visible name, within the same eligibility and preview disclosure scope.","in":"query","name":"search","schema":{"type":"string"}},{"description":"Search facet values; uses live counts.","in":"query","name":"q","schema":{"type":"string"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): words that must each match whole. Every word of every value (values split at whitespace) must be in the record's family search fields or its linked organization's visible name (the search fields a family sees, as a family sees them, so hidden text never matches) as a whole word or a word of the same English stem, case-insensitively (art matches Arts but not party; camps matches camp); a value's words may match in different fields. Stop words (the, for) are ignored, so a value of stop words alone keeps every row. ANDed with contains, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters; at most 10 words in all. For partial words, send contains.","in":"query","name":"text","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): text that each must contain. Each value matches when any visible search field contains it anywhere, case-insensitively, as one q value does (art matches Arts and party), as the reader sees the field, so hidden text never matches. ANDed with each other, with text, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters. Very common fragments of a few letters are the slowest searches. Activity matching also includes the linked organization's visible name, within the same eligibility and preview disclosure scope.","in":"query","name":"contains","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Sessions with an attended date on or after this date, or a date_range ending on or after it or stating no end (an ongoing session). On activities (family projection), an activity with such a session.","in":"query","name":"from","required":false,"schema":{"type":"string","format":"date"}},{"description":"Sessions with an attended date on or before this date, or a date_range starting on or before it. On activities (family projection), an activity with such a session.","in":"query","name":"to","required":false,"schema":{"type":"string","format":"date"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated YYYY-MM-DD dates, at most 31: a session attending any of them or whose date_range contains one (a date_range without an end runs on).","in":"query","name":"dates","required":false,"schema":{"type":"string","example":"2027-11-26,2027-11-27"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. true keeps sessions whose schedule covers a Saturday or Sunday: an attended weekend date (explicit dates or a weekday rule), or a date_range whose days include one. A range of a week or more, or one with a start and no end, always matches; a range without a start never does.","in":"query","name":"weekends","required":false,"schema":{"type":"boolean"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated morning, afternoon, evening, by the session's earliest start: before 12:00, before 17:00, then later.","in":"query","name":"timeOfDay","required":false,"schema":{"type":"string","example":"morning,afternoon"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated half_day, full_day, overnight. half_day and full_day compare the longest same-day span of non-overnight clocks; overnight is the session's explicit overnight attribute.","in":"query","name":"dayLength","required":false,"schema":{"type":"string","example":"full_day"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated before, after; every listed care must be offered (by the session's schedule or configuration, or for an activity by itself or any of its sessions). A configuration offers care when its availability is available (or a truthy value stored before normalization) or it gives the care clock.","in":"query","name":"care","required":false,"schema":{"type":"string","example":"before,after"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole US dollars. Keeps sessions whose sole stated price (session_prices) is at most priceMax: at priceBasis when it is given, else at whatever basis the price states, so a cap reads \"up to $N, whatever the period\". A price without a stated price_basis counts as session (the whole enrollment). A session with several quotes states no sole price and never matches; an activity matches through any of its live sessions. Other currencies are excluded.","in":"query","name":"priceMax","required":false,"schema":{"type":"integer","minimum":0}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. The basis priceMax compares at; requires priceMax. Without it, priceMax compares each price at the basis it states.","in":"query","name":"priceBasis","required":false,"schema":{"type":"string","enum":["hour","day","week","month","session"]}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. true keeps sessions whose availability is open or limited on the household-local today and that have not ended (a session without a stated end is ongoing).","in":"query","name":"openSpots","required":false,"schema":{"type":"boolean"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated whole years, each 0 to 17 (children of different ages): keeps records whose age_range_years contains any of them. With ageMin or ageMax, a record that fits any listed age or overlaps that range matches. A session that states no ages of its own is read with its activity's.","in":"query","name":"ages","required":false,"schema":{"type":"string","example":"4,11"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole years; keeps records whose age_range_years overlaps [ageMin, ageMax] (either end open), or fits one of ages when that is given too; a session that states no ages of its own is read with its activity's.","in":"query","name":"ageMin","required":false,"schema":{"type":"integer","minimum":0,"maximum":17}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole years; see ageMin.","in":"query","name":"ageMax","required":false,"schema":{"type":"integer","minimum":0,"maximum":17}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated camps, classes, after_school, mapped onto the activity's format and seasonality.","in":"query","name":"program","required":false,"schema":{"type":"string","example":"camps"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated feed interest ids (at most 20): offerings tagged with any of them.","in":"query","name":"interest","required":false,"schema":{"type":"string","example":"robotics,painting"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Origin for distanceMiles, radiusMiles and orderBy=distance: home (the household's searchArea.point; 409 when it is unset; 400 for the consumer site key) or latitude,longitude in decimal degrees.","in":"query","name":"near","required":false,"schema":{"type":"string","example":"47.61,-122.33"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Keeps rows whose own coordinates are within this many miles of near; rows without coordinates are excluded. Requires near.","in":"query","name":"radiusMiles","required":false,"schema":{"type":"integer","minimum":1,"maximum":50}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. A map's view: west,south,east,north in decimal degrees (west greater than east crosses the antimeridian; south below north). Keeps rows whose point (their own, else the one they inherit) is street-grade (coordinate_precision exact, building, parcel or street) and inside the view; a ZIP or state centre is never in view. Combines with every other filter, near, orderBy=distance and fields=pins; counts (limit=0) count the rows in view. 400 when it is not four finite numbers describing a nonempty view.","in":"query","name":"bbox","required":false,"schema":{"type":"string","example":"-122.75,45.45,-122.55,45.6"}},{"description":"Consumer site key only; any other credential gets 400 (a signed-in family reads in its household time zone). The visitor's IANA time zone (UTC or an Area/Location tz database name), which sets the day availability, nextSessions and date filters read from. Without it, signed-out reads use the launch metro's zone (America/Los_Angeles). An unknown or process-dependent name (Local) is 400.","in":"query","name":"tz","required":false,"schema":{"type":"string","example":"America/Los_Angeles"}},{"description":"Case-insensitive exact city name or its lowercase hyphenated slug.","in":"query","name":"city","required":false,"schema":{"example":"seattle","type":"string"}},{"description":"Case-insensitive exact region, such as a state abbreviation.","in":"query","name":"region","required":false,"schema":{"example":"WA","type":"string"}},{"description":"Exact category recorded on this activity; no parent inheritance.","in":"query","name":"category","required":false,"schema":{"example":"STEM","type":"string"}},{"description":"Returns records whose stated whole completed age range in inclusive age_range_years bounds includes this age in years.","in":"query","name":"ageYears","required":false,"schema":{"example":10,"maximum":8333,"minimum":0,"type":"integer"}},{"description":"Legacy compatibility filter for month-based ages. New clients should use whole-year ageYears; when a whole-year range is sent through a legacy month-shaped adapter, inclusive bounds expand as [yearMin*12,yearMax*12+11].","in":"query","name":"ageMonths","required":false,"schema":{"example":120,"maximum":100000,"minimum":0,"type":"integer"}},{"description":"Returns records whose stated grade range includes this grade. Use -1 for pre-K and 0 for kindergarten.","in":"query","name":"grade","required":false,"schema":{"example":5,"maximum":12,"minimum":-1,"type":"integer"}},{"description":"Inclusive minimum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response.","in":"query","name":"minimumScore","required":false,"schema":{"example":81,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Inclusive maximum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response. Must not be below minimumScore.","in":"query","name":"maximumScore","required":false,"schema":{"example":100,"maximum":100,"minimum":0,"type":"integer"}},{"name":"filter","in":"query","required":false,"description":"One URL-encoded JSON predicate tree, sent once. Combines with all legacy filters using AND before rows, totals and facet counts are selected. Read /api/canonical/schema tables[*].filterCapabilities for visible fields, operators, relations and limits. Invalid/unknown/over-complex filters return 400; unavailable fields return 403, including in nonmatching branches. Do not broaden a failed request by dropping predicates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalFilter"}}}},{"name":"organizationId","in":"query","required":false,"description":"Exact organization UUID. Selects the activity own organization.","schema":{"type":"string","format":"uuid"}},{"name":"skillLevel","in":"query","required":false,"description":"Exact recorded skill_level value.","schema":{"type":"string"}},{"name":"format","in":"query","required":false,"description":"Exact recorded format value.","schema":{"type":"string"}},{"name":"seasonality","in":"query","required":false,"description":"Exact recorded seasonality value.","schema":{"type":"string"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated format kinds (day_camp, overnight_camp, class, drop_in, event): activities whose formatKind is any of them (a session through its activity), read from activity_format_facts. An unknown value is 400.","in":"query","name":"formatKind","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Existing success envelope containing facets, tier, resultLimit, and sourceClass. A single field returns values with value, label, and rows. Without field (all facets), each free-text facet (city, region, category, skillLevel, format, seasonality) lists at most facetValueLimit values (default 100, a tunable policy), its most common by count; a value the request filters that field on is always listed, first. facetValueLimit is in the envelope. Read one facet with field=, limit and q for the rest."},"400":{"description":"Invalid field or snapshot option. Also returned for malformed, repeated, unknown, over-complex or incorrectly typed structured filter JSON."},"401":{"description":"Authentication required."},"402":{"description":"Starter or higher required."},"409":{"description":"near=home when the family's household has neither a searchArea.point nor a known searchArea.postalCode (a co-parent's or caregiver's home is the family owner's)."},"429":{"description":"Request allowance exceeded, with Retry-After. A data-API key's hourly read allotment answers retryAfterMs, readsAllotment and windowMs. A signed-in family session or the consumer site key answers its per-minute app limit with retryAfterMs and windowMs only (the FamilyRequestLimit shape)."},"403":{"description":"A structured filter names a field or relationship outside current tier or preview disclosure."}},"summary":"Read activities facet or chart counts","tags":["Canonical"],"x-auth":"Account or named integration API key on Starter or above. Facet fields follow the owner’s current plan. A consumer's own signed-in account reads the family facets at any tier, from sign-up on. The parent site's catalog-only consumer site key (SCHOOLS_OUT_CATALOG_API_KEY) also reads it for signed-out visitors, as a Free family with no household: the family fields only, near as latitude,longitude (near=home is 400), and the day in the visitor's tz (default: the launch metro's zone).","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/activities/facets\" -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" --data-urlencode \"field=category\" --data-urlencode \"snapshot=true\"","title":"Read activities chart counts"}]}},"/api/activities/{id}":{"get":{"description":"Returns the tier-masked activity and related summaries. Activities have no enrollment price, currency, discount, or care-pricing fields. Session care quotes follow price access rules. Its image_url compatibility read returns the selected active cover only. Pricing remains tier-masked, including nested schedule quotes. Normal JSON records omit bookkeeping and ingestion history, null and blank record fields, and unknown availability, registration-status, and coordinate-precision values. Zero, false, empty arrays, masked values, positional range bounds, and structured schedule states retain their meanings. Related entity summaries follow the same projection; CSV retains its existing contract. Nonblank configuration_id is retained as record identity metadata for feedback targeting and omitted from display column metadata. Moved fields are external_ids, created_at, updated_at, source_created_at, field_provenance, source_records, merge_jobs, and relationship_jobs. Read permitted bookkeeping and provenance at /api/activities/{id}/provenance. Age ranges use age_range_years; normal JSON records and column metadata omit the redundant age_range_months alias. CSV and the legacy ageMonths filter retain month compatibility. Records and column metadata omit score. Read /api/activities/{id}/score for live detail or /api/activities/scores?ids=UUID,UUID&cached=true for persisted summaries. Normal detail responses include data.media, the first gallery page of up to 100 images, with entity, items, coverId, logoId, total, limit, and offset. Images include displayUrl, label, kind, width, and height; source attribution is omitted even for ADMIN. Previews omit media. Optional includeMedia=false skips the gallery; omitted or true includes it. The parameter accepts one exact true or false value, with malformed or repeated values returning 400. Use the existing /media endpoint for additional pages or authorized source attribution. On /media, includeProvenance=false skips source attribution; omitted or true retains existing authority-gated sources. Use limit=1 at the selected image offset when requesting only its source history. The record's independent overnight boolean is true for explicit overnight status and false for explicit day status; unknown or mixed status is omitted. It never inherits a parent's status. A consumer's own signed-in account reads at any tier, from sign-up on with the family fields added: data.activity carries its tier's fields (none below Starter) plus the FamilyCatalogRecord fields, derived availability and nextSessions; data.activity.sessions lists the first sessionsLimit live sessions by start date, with availability, and sessionsTotal counts them all; data.projection is family; lockedColumns lists what the tier locks and the family fields do not show (empty below Starter). Ordinary permitted JSON records include resolved_cover with the ultimate source entity, inheritance flag, and structured stock attribution. Owned gallery and image compatibility fields retain their existing meaning. Detail responses include data.tier and data.lockedColumns for this record kind without requiring a list read; preview and family reads retain their existing disclosure overrides.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"name":"includeMedia","in":"query","required":false,"description":"One exact true or false value. Omitted or true bundles the first gallery page; false skips media for follow-up diagnostic reads. Previews always omit media.","schema":{"type":"boolean","default":true}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. How many sessions (by start date) the detail lists, from 1 to 500; default 100. sessionsTotal counts them all.","in":"query","name":"sessionsLimit","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"description":"Consumer site key only; any other credential gets 400 (a signed-in family reads in its household time zone). The visitor's IANA time zone (UTC or an Area/Location tz database name), which sets the day availability, nextSessions and date filters read from. Without it, signed-out reads use the launch metro's zone (America/Los_Angeles). An unknown or process-dependent name (Local) is 400.","in":"query","name":"tz","required":false,"schema":{"type":"string","example":"America/Los_Angeles"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"name":"includeResolution","in":"query","required":false,"description":"Include field_resolution on each returned entity row: own, inherited, or unset for each projected field. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeSources","in":"query","required":false,"description":"Include field_sources on each returned entity row, mapping fields to the canonical entity supplying their effective value or configuration unit. Independent of includeResolution; defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeOwnValues","in":"query","required":false,"description":"Include own_values on each returned entity row, retaining null local values. Requires editor authorization; otherwise returns 403. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"activity":{"anyOf":[{"$ref":"#/components/schemas/CanonicalDetailRow"},{"$ref":"#/components/schemas/FamilyCatalogRecord"}]},"media":{"$ref":"#/components/schemas/CanonicalMediaPage"},"tier":{"type":"string"},"lockedColumns":{"type":"array","description":"Fields withheld for this record kind under the effective tier, preview, or family projection.","items":{"type":"object","required":["column","requiredTier"],"properties":{"column":{"type":"string"},"requiredTier":{"type":"string"}}}}},"required":["activity","tier","lockedColumns"],"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Canonical activity detail"},"404":{"$ref":"#/components/responses/NotFound"},"400":{"description":"Invalid detail parameters, including malformed or repeated includeMedia."},"429":{"description":"Request allowance exceeded, with Retry-After. A data-API key's hourly read allotment answers retryAfterMs, readsAllotment and windowMs. A signed-in family session or the consumer site key answers its per-minute app limit with retryAfterMs and windowMs only (the FamilyRequestLimit shape)."},"403":{"description":"showRemoved=true requires a current staff or ADMIN operator; preview and non-operator viewers cannot opt in."}},"summary":"Read one canonical activity","tags":["Organization catalog"],"x-auth":"Account cookie or header, or permitted machine key, from Starter up. The parent site's catalog-only consumer site key (SCHOOLS_OUT_CATALOG_API_KEY) also reads it for signed-out visitors, as a Free family with no household: the family fields only, near as latitude,longitude (near=home is 400), and the day in the visitor's tz (default: the launch metro's zone).","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/activities/$ACTIVITY_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Read one activity"}]}},"/api/activities/{id}/media":{"get":{"description":"A consumer's own signed-in account may read media at any tier. The consumer site key may not; its detail reads take includeMedia=true. Returns this record’s owned paginated gallery and selections plus resolvedCover for display. Inherited covers retain their owning record and do not change gallery membership or counts. Stock covers include public provider and photographer credits. Remote URLs and R2 URLs are served directly; uploaded assets retain protected URLs, except the selected logo (logoId), which once rendered is its public, cross-origin logo rendition URL (GET /api/media/logos/{file}). Source evidence is available only to non-preview ADMIN accounts. Pagination uses the existing catalog bounds. Authorized showRemoved=true retains this gallery and its selections for staff inspection. Protected uploaded URLs under /api/organizations/{id}/brand/assets/{assetId} remain readable by current operators after removal under the existing brand-read access checks; ordinary viewers cannot use those binary reads to inspect removed records.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Defaults to 30 when absent or invalid; values over 100 cap at 100.","in":"query","name":"limit","schema":{"default":30,"maximum":100,"minimum":1,"type":"integer"}},{"description":"Defaults to 0 when absent or invalid; values over 1000000 cap at 1000000.","in":"query","name":"offset","schema":{"default":0,"maximum":1000000,"minimum":0,"type":"integer"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/CanonicalMediaPage"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Paginated media and selected IDs"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Starter access is required."},"403":{"description":"Previews cannot read media."},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Read activity media","tags":["Organization catalog"],"x-auth":"Starter or above; previews are denied. Resolved covers follow the same media access gates.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/activities/$ACTIVITY_ID/media\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"limit=30\" \\\n  --data-urlencode \"offset=0\"","title":"Read the first page of an activity gallery"}]}},"/api/assistant":{"post":{"description":"Assistant answers in short conversational replies using the same tier-filtered read-only canonical database access as Ask. Greetings, acknowledgments, clarification, and general planning need no query and can have empty results, sources, and limitations. After a query is attempted, the answer must cite usable evidence or explain the data limitation. Listing facts still require current-turn evidence. Optional browser history (at most 16 exchanges, 24,000 characters and 10 result references each) is untrusted and not stored. Alternatively, conversation {requestId, id?} saves this turn in an account-owned conversation and uses its stored completed turns as history; history and conversation cannot be combined. A repeated requestId returns the stored answer as {replayed:true, credits:null} without generating or charging; a pending or failed requestId returns 409, and only one turn per conversation can be pending. includeFamilyContext:true adds the viewer's GET /api/users/viewer/family-context projection as saved data. Family context and saved conversations use the family policy and refuse service delegation. A parent asks free as their own signed-in, email-verified account: each question counts against a daily per-account limit and a daily budget shared by all accounts (UTC days) and spends no Ask credits, so data.credits is null; staff and ADMIN accounts are not counted and charge the existing Ask allowance, which exempts them. Generation failures return the question or refund the charge and settle a saved turn as failed or cancelled. Streams NDJSON statuses and a terminal result with data.response and data.credits (and data.saved for saved turns); every event of a saved turn carries conversation {id, turnId}. Each result references an organization, activity or session root identifier from a returned row, with that row as record and the price, age, location, date, or time facts it left empty (missing) or did not select (unchecked). Assistant never saves family information. Disconnect cancels unfinished generation. Result assessments reuse returned organization freshness/confidence and source dates, preserve activity/session fact scope, and expose missing or unselected price, age, hours and availability facts. Organization scores do not verify specific facts. Price periods, mandatory fees and current places remain unconfirmed without supporting evidence. Ambiguous rows and known unsupported verification-recency wording share one bounded correction attempt with explicit uncertainty on repeated failure. Responses may include up to three preferenceProposals for parent review: budget, search_area, child_interests, memory_fact (a statement, category and optional permitted childId for a lasting family fact), or search_recap. Each quote is an exact current-question substring, not listing/history evidence; invalid proposals are discarded. Unknown dimensions are null and must be completed during review. Legacy responses may omit preferenceProposals. A proposal neither saves nor approves anything. After explicit review, the parent uses the existing versioned household or child PATCH endpoint with the actual completed turn source. A reviewed memory_fact is saved with POST /api/users/viewer/family-memory, approve:true and that same source. Saving requires saved:true, replayed:true, or a completed turn returned by the conversation API; saved:false is not a completed source. Reload after conflicts or uncertain saves; do not retry against a new version without review. With webSearch:true, the catalog still comes first: web search is offered to the model only after a returned listing shows a selected fact with no value or stale organization evidence, or after it tried to finish without a catalog result (a single empty query is widened once first). The model never writes the search text: the server builds it from fixed fields, refuses a child's label and any word from saved requirements, notes or remembered facts the parent did not type in this conversation, screens a query that goes beyond the parent's own words for health, support or family circumstances, and runs it in a separate provider request that sees nothing else. Web pages appear in sources as {evidenceId: web-N, sourceClass: web, url, title, providerSite}; only pages a search retrieved in that turn are listed, and everything they say is advertised, not confirmed. Clients that validate sources must accept the web class before sending webSearch:true. Without it, answers and sources are unchanged. A search_recap proposal records which shown options the parent chose or ruled out, with server-supplied names and a ready memoryFact for the parent to approve; options are nameable only from this answer's results or a saved conversation. For the adult’s own session, Mellow may search/read connected Gmail on an email-related request and prepare an application email review. emailSources carries retrieved message metadata and emailDraft the editable review; sending requires a separate confirmed Gmail send request. Group answers never receive these tools. An optional emailPrompt {reason} stages a contextual Gmail step for a specific unresolved school date, existing booking detail or requested provider reply. It is absent/null for generic browsing, already-answered email turns and groups. It does not itself read Gmail or resume the question: the parent explicitly chooses Connect Gmail or Check my email in the client. Web search is disabled after a Gmail search, read or draft starts in the same turn, so private email content cannot become an outgoing web query. Server-owned emailContext:true persists this privacy boundary through later saved answers. Personal Gmail-capable requests with stateless browser history also disable web search, because that history has no trustworthy email provenance. subject {kind: organization|activity, id: canonical UUID} names the camp or activity page the parent asked from: before the model answers, the server searches that organization's activities, or that activity and its sessions, through the same search_canonical path and query budget as the model (family parameters organizationId/activityId for the parent's own account, the structured filter otherwise), so those rows are current-turn evidence the answer can cite and show as results (at most 10). A subject that returns nothing for this account does not fail the answer. A malformed subject, an unknown kind or a non-UUID id returns 400 before charging. The saved turn keeps only the parent's question; the subject is not stored, and a replayed requestId returns the stored answer. New card responses use fewer than 25 words for framing and one question or next step, without narrating card details. A bounded presentation repair checks length, multiple questions and known canonical names missing a card or repeated in prose; it is not a general semantic verifier. Repeated presentation failures keep valid cards with concise fallback text. Routine missing-field diagnostics remain in result assessments instead of being added to parent-facing limitations. Clients must accept organization result/history references before this response contract is deployed.","requestBody":{"content":{"application/json":{"schema":{"properties":{"conversation":{"additionalProperties":false,"properties":{"id":{"description":"Continue this conversation; omit to start one.","format":"uuid","type":"string"},"requestId":{"description":"Client-generated idempotency key for this turn.","pattern":"^[A-Za-z0-9_-]{8,64}$","type":"string"}},"required":["requestId"],"type":"object"},"history":{"description":"Earlier exchanges in the current conversation, oldest first. Unknown fields are refused.","items":{"additionalProperties":false,"properties":{"answer":{"maxLength":6000,"minLength":1,"type":"string"},"question":{"maxLength":5000,"minLength":1,"type":"string"},"results":{"description":"Results that answer showed, in order, so a later reply such as \"the second one\" can refer to them.","items":{"additionalProperties":false,"properties":{"id":{"format":"uuid","type":"string"},"kind":{"enum":["organization","activity","session"]}},"required":["kind","id"],"type":"object"},"maxItems":10,"type":"array"}},"required":["question","answer"],"type":"object"},"maxItems":16,"type":"array"},"includeFamilyContext":{"default":false,"description":"Add the viewer's approved family context as saved data, never instructions.","type":"boolean"},"question":{"maxLength":5000,"minLength":1,"type":"string"},"webSearch":{"default":false,"description":"Allow a web search fallback after the catalog has been tried. Answers may then include sources with sourceClass web.","type":"boolean"},"subject":{"additionalProperties":false,"description":"The camp (organization) or activity page the parent asked from. The server searches its own listings first, so the answer is about them unless the parent asks for alternatives. Not stored with a saved turn.","properties":{"id":{"format":"uuid","type":"string"},"kind":{"enum":["organization","activity"],"type":"string"}},"required":["kind","id"],"type":"object"},"documentIds":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":3,"uniqueItems":true,"description":"Your children's documents to read with this question (at most 15 MiB together). Only the family's owner may attach them; a saved conversation keeps their ids on the turn, never the files."}},"required":["question"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/x-ndjson":{"schema":{"oneOf":[{"properties":{"status":{"properties":{"detail":{"type":"string"},"phase":{"type":"string"},"title":{"type":"string"}},"required":["phase","title"],"type":"object"},"type":{"const":"status"}},"required":["type","status"],"type":"object"},{"properties":{"data":{"properties":{"credits":{"properties":{"allotment":{"type":["integer","null"]},"balance":{"type":["integer","null"]},"charged":{"type":"integer"},"lifetime":{"type":"boolean"},"pooled":{"type":"boolean"},"unlimited":{"type":"boolean"}},"type":"object"},"response":{"additionalProperties":false,"properties":{"agent":{"const":"Assistant"},"answer":{"type":"string","description":"Short framing or reaction and one question or supported next step. New answers with cards use fewer than 25 words and do not repeat card names or details."},"limitations":{"items":{"type":"string"},"type":"array","description":"Explicitly requested uncertainty or a query failure, not an inventory of missing card fields. Detailed coverage remains in each result assessment."},"preferenceProposals":{"description":"Optional for historical responses; new answers return an array. Quotes are exact current-question substrings, capped at 500 characters. Budget amounts have at most two decimal places. Non-null child IDs must occur in the supplied permitted family context. Parent review remains required; quotation does not certify the interpretation.","items":{"anyOf":[{"additionalProperties":false,"properties":{"budget":{"additionalProperties":false,"properties":{"amount":{"maximum":1000000,"minimum":0,"type":["number","null"]},"currency":{"pattern":"^[A-Z]{3}$","type":["string","null"]},"limit":{"enum":["hard_cap","preference",null],"type":["string","null"]},"period":{"enum":["day","week","month","session","season","year","total",null],"type":["string","null"]},"scope":{"enum":["per_child","household",null],"type":["string","null"]}},"required":["amount","currency","period","scope","limit"],"type":"object"},"kind":{"enum":["budget"],"type":"string"},"quote":{"maxLength":500,"minLength":1,"type":"string"}},"required":["kind","quote","budget"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"enum":["search_area"],"type":"string"},"quote":{"maxLength":500,"minLength":1,"type":"string"},"searchArea":{"additionalProperties":false,"properties":{"label":{"maxLength":120,"minLength":1,"type":"string"},"postalCode":{"pattern":"^[0-9]{5}$","type":["string","null"]},"region":{"pattern":"^[A-Z]{2}$","type":["string","null"]}},"required":["label","region","postalCode"],"type":"object"}},"required":["kind","quote","searchArea"],"type":"object"},{"additionalProperties":false,"properties":{"childId":{"pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":["string","null"]},"interests":{"items":{"maxLength":100,"minLength":1,"type":"string"},"maxItems":20,"minItems":1,"type":"array"},"kind":{"enum":["child_interests"],"type":"string"},"quote":{"maxLength":500,"minLength":1,"type":"string"}},"required":["kind","quote","childId","interests"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"enum":["memory_fact"],"type":"string"},"memoryFact":{"additionalProperties":false,"properties":{"category":{"enum":["activity_history","logistics","preference","other"],"type":"string"},"childId":{"pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":["string","null"]},"statement":{"maxLength":500,"minLength":1,"type":"string"}},"required":["statement","category","childId"],"type":"object"},"quote":{"maxLength":500,"minLength":1,"type":"string"}},"required":["kind","quote","memoryFact"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"enum":["search_recap"],"type":"string"},"memoryFact":{"additionalProperties":false,"description":"The record the server wrote from the named options, ready for the parent to approve through the family-memory route.","properties":{"category":{"enum":["activity_history","logistics","preference","other"],"type":"string"},"childId":{"pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":["string","null"]},"statement":{"maxLength":500,"minLength":1,"type":"string"}},"required":["statement","category","childId"],"type":"object"},"quote":{"maxLength":500,"minLength":1,"type":"string"},"searchRecap":{"additionalProperties":false,"properties":{"chosen":{"items":{"additionalProperties":false,"properties":{"id":{"pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"kind":{"enum":["organization","activity","session"],"type":"string"},"name":{"description":"The listing's name from the row the viewer was shown.","maxLength":60,"type":"string"}},"required":["kind","id","name"],"type":"object"},"maxItems":3,"type":"array"},"ruledOut":{"items":{"additionalProperties":false,"properties":{"id":{"pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"kind":{"enum":["organization","activity","session"],"type":"string"},"name":{"description":"The listing's name from the row the viewer was shown.","maxLength":60,"type":"string"},"reason":{"description":"The parent's stated reason, when given.","maxLength":80,"type":"string"}},"required":["kind","id","name"],"type":"object"},"maxItems":5,"type":"array"},"summary":{"maxLength":150,"minLength":1,"type":"string"}},"required":["summary","chosen","ruledOut"],"type":"object"}},"required":["kind","quote","searchRecap","memoryFact"],"type":"object"}]},"maxItems":3,"type":"array"},"question":{"type":"string"},"results":{"items":{"additionalProperties":false,"properties":{"assessment":{"$ref":"#/components/schemas/AssistantEvidenceAssessment"},"evidenceId":{"type":"string"},"id":{"format":"uuid","type":"string"},"kind":{"enum":["organization","activity","session"]},"missing":{"items":{"enum":["price","ages","location","dates","times"]},"type":"array"},"record":{"description":"The viewer's returned row for this result.","type":"object"},"unchecked":{"items":{"enum":["price","ages","location","dates","times"]},"type":"array"},"fit":{"type":"object","additionalProperties":false,"required":["verdict","childIds","reason"],"properties":{"verdict":{"type":"string","enum":["fits","not_a_fit"]},"childIds":{"type":"array","maxItems":8,"items":{"type":"string","format":"uuid"}},"reason":{"type":"string","maxLength":120}},"description":"Optional model conclusion for permitted family children, not verified eligibility. Omitted when no permitted child is named and in group answers. A not_a_fit verdict includes a reason."}},"required":["kind","id","evidenceId","record","missing","unchecked","assessment"],"type":"object"},"maxItems":10,"type":"array","description":"Interactive references for every organization, activity or session presented. References must identify current-turn canonical roots; nested parent summaries and web-only findings cannot mint cards."},"sources":{"items":{"oneOf":[{"additionalProperties":false,"properties":{"evidenceId":{"type":"string"},"fields":{"items":{"type":"string"},"type":"array"},"rows":{"minimum":0,"type":"integer"},"sourceClass":{"const":"canonical"}},"required":["evidenceId","sourceClass","fields","rows"],"type":"object"},{"additionalProperties":false,"properties":{"evidenceId":{"pattern":"^web-[1-9][0-9]*$","type":"string"},"providerSite":{"description":"Whether the page is on the activity provider's own website.","type":"boolean"},"sourceClass":{"const":"web"},"title":{"description":"Untrusted page title for display.","maxLength":200,"type":"string"},"url":{"description":"A page the web search retrieved in this turn.","format":"uri","type":"string"}},"required":["evidenceId","sourceClass","url","title","providerSite"],"type":"object"}]},"type":"array"},"suggestedReplies":{"description":"Short tappable labels answering one question, sent as the next real user turn in the same conversation. New labels are at most 40 characters; historical saved answers may contain up to 120. Chips never authorize a booking, payment, email send or family write.","items":{"maxLength":120,"minLength":1,"type":"string"},"maxItems":4,"type":"array"},"emailSources":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/AssistantEmailSource"},"description":"Messages actually retrieved from the signed-in adult’s inbox in this turn. Optional for historical answers. Raw message bodies and attachments are not returned here."},"emailDraft":{"$ref":"#/components/schemas/AssistantEmailDraft"},"emailPrompt":{"$ref":"#/components/schemas/AssistantEmailPrompt"},"emailContext":{"const":true,"description":"Optional server-produced privacy marker. This answer may carry private email context from this or an earlier saved turn, or from untrusted stateless history. Subsequent saved answers retain it and cannot use web search. It grants no Gmail or send authority; clients may ignore it and cannot submit it as history provenance."},"actionProposals":{"type":"array","maxItems":3,"description":"Optional in historical answers; new answers include up to three exact-current-message proposals about a grounded result or trusted saved result. Each requires the parent's separate confirmation through its existing write route. Empty in groups.","items":{"type":"object","additionalProperties":false,"required":["kind","quote","targetKind","targetId","targetName"],"properties":{"kind":{"type":"string","enum":["save","follow","record_plan"]},"quote":{"type":"string"},"targetKind":{"type":"string","enum":["activity","session"]},"targetId":{"type":"string","format":"uuid"},"targetName":{"type":"string"},"alerts":{"type":"array","items":{"type":"string","enum":["registration_opens","spot_opens","dates_posted","updates"]}},"childIds":{"type":"array","items":{"type":"string","format":"uuid"}},"status":{"type":"string","enum":["registered","applied"]}}}}},"required":["agent","question","answer","suggestedReplies","results","sources","limitations"],"type":"object"}},"required":["response","credits"],"type":"object"},"success":{"const":true},"type":{"const":"result"}},"required":["type","success","data"],"type":"object"},{"properties":{"data":{"type":"null"},"error":{"const":true},"message":{"type":"string"},"type":{"const":"error"}},"required":["type","error","message","data"],"type":"object"}]}}},"description":"Newline-delimited JSON status events followed by one result or error event."},"400":{"description":"Missing, invalid, or oversized question."},"401":{"description":"Authentication required."},"402":{"description":"A staff or ADMIN account's Ask allowance is unavailable or exhausted."},"403":{"description":"A parent without a session-backed credential of their own, an unverified email, origin or CSRF denied, or a machine key or preview session. Returned before generation."},"413":{"description":"Request body exceeds 32 KiB."},"429":{"description":"Account request allowance exceeded, or the parent asked the day's free questions; Retry-After gives the seconds until the next UTC day."},"502":{"description":"Account allowance could not be confirmed."},"503":{"description":"Agent configuration or canonical scoring readiness unavailable, or every account together asked the day's free question budget; Retry-After gives the seconds to wait."}},"security":[{"AccountCredential":[]}],"summary":"Ask Assistant a question using read-only canonical data","tags":["Internal assistant"],"x-auth":"Any account as the parent's own session-backed credential (an account key with X-Session-Id, or a signed Intelligence session naming a live session) with CSRF from a trusted or API_ASSISTANT_ORIGINS origin, and a verified email. Staff or ADMIN accounts, decided from the stored account, may also use a bare account credential, and service delegation to them is allowed only without includeFamilyContext or conversation. Machine keys and investor-preview sessions are refused.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/assistant\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"question\":\"Only afternoons\",\"includeFamilyContext\":true,\"conversation\":{\"id\":\"$CONVERSATION_ID\",\"requestId\":\"turn-0002\"}}'","title":"Continue a saved conversation with family context"}],"x-internal":true}},"/api/assistant/suggestions":{"get":{"description":"The questions Assistant offers before the parent types one (at most a few, best first), written from the viewer's family context: children with their ages and interests, the search area, and the next school day off nothing covers. With subjectKind (organization or activity) and subjectId, they are about that camp or activity page, read through the same scoped search as POST /api/assistant's subject, and each carries {kind, id} as subject to send back with its question; a listing this account cannot read leaves them general (subject null). label is the chip's words and question is what is asked. Wording and choice are server policy, not a contract.","parameters":[{"name":"subjectKind","in":"query","required":false,"schema":{"enum":["organization","activity"]}},{"name":"subjectId","in":"query","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","required":["suggestions"],"properties":{"suggestions":{"type":"array","items":{"type":"object","required":["label","question","subject"],"properties":{"label":{"type":"string"},"question":{"type":"string"},"subject":{"type":["object","null"],"properties":{"kind":{"enum":["organization","activity"]},"id":{"type":"string","format":"uuid"}},"required":["kind","id"]}}}}}}}}}}},"400":{"description":"subjectKind and subjectId must name an organization or activity together"},"401":{"description":"Current account required"},"403":{"description":"Not a family account, staff or ADMIN"}},"security":[{"AccountCredential":[]}],"summary":"List the questions Assistant offers","tags":["Internal assistant"],"x-auth":"Current account cookie or account credential only; delegated service requests, machine keys and investor previews are refused. Staff and ADMIN accounts may also ask through a delegated service, without family context.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/assistant/suggestions?subjectKind=activity&subjectId=ACTIVITY_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Suggestions on an activity page"}],"x-internal":true}},"/api/assistant/conversations":{"get":{"description":"Returns up to 200 of the account's conversations, most recently updated first, without turns. Conversations are kept until deleted or the account is deleted.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"conversations":{"items":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"title":{"type":"string"},"turns":{"items":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"position":{"type":"integer"},"question":{"type":"string"},"requestId":{"type":"string"},"response":{"type":["object","null"]},"settledAt":{"format":"date-time","type":["string","null"]},"status":{"enum":["pending","completed","failed","cancelled"]}},"required":["id","position","requestId","status","question","response","createdAt","settledAt"],"type":"object"},"type":"array"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","title","createdAt","updatedAt"],"type":"object"},"type":"array"}},"required":["conversations"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"},"403":{"description":"Not the viewer's own account session or credential"}},"security":[{"AccountCredential":[]}],"summary":"List saved Assistant conversations","tags":["Internal assistant"],"x-auth":"Current account cookie or account credential only; delegated service requests, machine keys and investor previews are refused. Conversations are private to their owner. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/assistant/conversations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List saved conversations"}],"x-internal":true}},"/api/assistant/conversations/{conversationId}":{"delete":{"description":"Permanently deletes the transcript and its turns and clears references to it from profile, household and care provenance and from remembered facts. Facts and values the parent approved remain because their approval is independent.","parameters":[{"in":"path","name":"conversationId","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"deleted":{"const":true}},"required":["deleted"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"403":{"description":"Not the viewer's own account session or credential"},"404":{"description":"Record not found for this account"}},"security":[{"AccountCredential":[]}],"summary":"Delete a saved Assistant conversation","tags":["Internal assistant"],"x-auth":"Current account cookie or account credential only; delegated service requests, machine keys and investor previews are refused. Conversations are private to their owner. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/assistant/conversations/$CONVERSATION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Delete a saved conversation"}],"x-internal":true},"get":{"description":"Returns every turn in order with its status. Only completed turns store an answer; failed and cancelled turns keep the accepted question. A pending turn older than five minutes is settled as failed by the next request in that conversation.","parameters":[{"in":"path","name":"conversationId","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"conversation":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"title":{"type":"string"},"turns":{"items":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"position":{"type":"integer"},"documents":{"description":"The child documents attached to this question, in the order sent. Only their ids are stored; title is the document's current title, or null once it is deleted.","items":{"properties":{"id":{"format":"uuid","type":"string"},"title":{"type":["string","null"]}},"required":["id","title"],"type":"object"},"type":"array"},"question":{"type":"string"},"requestId":{"type":"string"},"response":{"type":["object","null"]},"settledAt":{"format":"date-time","type":["string","null"]},"status":{"enum":["pending","completed","failed","cancelled"]}},"required":["id","position","requestId","status","question","response","createdAt","settledAt"],"type":"object"},"type":"array"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","title","createdAt","updatedAt"],"type":"object"}},"required":["conversation"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"403":{"description":"Not the viewer's own account session or credential"},"404":{"description":"Record not found for this account"}},"security":[{"AccountCredential":[]}],"summary":"Read a saved Assistant conversation","tags":["Internal assistant"],"x-auth":"Current account cookie or account credential only; delegated service requests, machine keys and investor previews are refused. Conversations are private to their owner. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/assistant/conversations/$CONVERSATION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read a saved conversation"}],"x-internal":true}},"/api/auth/gmail-callback":{"get":{"description":"Consumes the Google state and requires the starting browser nonce. Web flow exchanges the code, validates both Gmail scopes and stores the sealed grant, then returns gmail=connected, denied or failed. A proof flow (app or a site's server) only seals a short-lived authorization code and returns gmail=authorized&code=<one-use completion>; denial/failure includes no code. Only authenticated completion with the verifier may exchange and save it. Every valid-state proof outcome echoes challenge=<original public S256>; the app must match it to its pending attempt before completing or clearing its proof.","parameters":[{"description":"One-time browser state, checked against its nonce cookie.","in":"query","name":"state","required":true,"schema":{"type":"string"}},{"description":"Google authorization code; never forwarded to the app in plaintext.","in":"query","name":"code","required":false,"schema":{"type":"string"}},{"description":"Google denial indicator; raw provider error text is not forwarded.","in":"query","name":"error","required":false,"schema":{"type":"string"}}],"responses":{"302":{"description":"Allowed parent page or app callback with a bounded outcome and optional one-use native completion ticket."},"400":{"description":"Invalid, expired or reused flow, missing proof, or rejected Google grant."}},"security":[],"summary":"Return from Google Gmail consent","tags":["Internal Gmail"],"x-auth":"This endpoint is open to the public (Google redirect target); the one-time state and the starting browser's nonce cookie identify the parent","x-curl-examples":[{"code":"curl -i -X GET \"https://apis.schoolsoutapp.com/api/auth/gmail-callback?state=$STATE&code=$GOOGLE_CODE\" \\\n  -H \"Cookie: $GMAIL_NONCE_COOKIE\"","title":"Return from Google consent"}],"x-internal":true}},"/api/auth/gmail-start":{"get":{"description":"Consumes ticket, sets the HttpOnly callback nonce in the system browser, then redirects to Google consent. Expired or replayed tickets return 400. No account credential is accepted in a URL; the browser step alone cannot store a connection.","parameters":[{"description":"One-time native bootstrap ticket.","in":"query","name":"ticket","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"302":{"description":"Google consent redirect with the browser nonce cookie; no-store and no-referrer."},"400":{"description":"Invalid, expired or reused flow, missing proof, or rejected Google grant."}},"security":[],"summary":"Open native Gmail consent in its authentication browser","tags":["Internal Gmail"],"x-auth":"Public, independent-auth browser navigation. A one-use 10-minute native bootstrap ticket identifies the starting account and app proof.","x-curl-examples":[{"code":"curl -i -X GET \"https://apis.schoolsoutapp.com/api/auth/gmail-start?ticket=$TICKET\"","title":"Open the native bootstrap redirect"}],"x-internal":true}},"/api/canonical/map":{"get":{"description":"Accessible coordinates retain full stored precision on every plan; preview field masks apply before viewport membership and grouping. Stable world-anchored cells intersecting the requested viewport are returned in full, including records just outside its edges. Grid resolution coarsens to a bounded feature budget without sampling. coverage reports the full matching permitted population, while viewport reports complete displayed cells. Counts are records, not distinct camps. viewport.scoreBands always returns separate strong, good, fair, weak, and unavailable counts for organization, activity, and session. Each kind sums to viewport.counts for the same complete displayed cells, including approximate locations. Paid non-preview viewers receive only scores cached under each entity kind's current scoring version; missing, stale-version, and undisclosed scores count as unavailable. It is independent of includeCampScores and never calculates scores or waits for map projection refresh. Each entity retains its own configuration; child venues never inherit headquarters. Approximate source coordinates remain separate from address-grade points. Selected-region membership uses distance in meters from the viewer-masked stored point to the unsimplified boundary, with inclusive layer tolerances initially 100 m for neighborhoods, 500 m for cities/ZIPs, 2 km for counties, 5 km for metros, and 10 km for states. A point-centered tolerance circle may partially overlap the region; the center need not be inside, and records may match multiple regions. The same rule applies to address-grade and ZIP/state approximate points, without source-area polygon intersections or zoom dependence. Unknown, missing, masked, and invalid locations are excluded. Approximate quality remains separate. This does not change structured canonical/Ask location classifications. Optional includeCampPoints=true adds an organization-only campPoints GeoJSON collection from the same masked cohort and snapshot. Only exact/building/parcel/street precision is included, within the requested projected-coordinate bbox including its edges. Organization IDs remain distinct at shared coordinates; properties are empty by default. Optional includeCampNames=true additionally adds properties.name only when the camp name is disclosed, using the existing viewer-level text scrubbing and preview name masks. It requires includeCampPoints=true or returns HTTP 400. Omitted/false adds no name property and performs no camp-point name reads. Hidden or missing names have no fallback label. Names share each camp point's masked coordinate, filtered cohort, and snapshot; groups never supply camp labels. The frontend requests this projection only at label zoom and uses collision-managed native symbols. Additional includeCampScores=true adds properties.scoreBand from the persisted organization score and requires includeCampPoints=true or returns HTTP 400. Bands are weak, fair, good, strong, or unavailable and use the same thresholds as organization score details. Missing prepared scores, restricted accounts, and every preview receive unavailable. Omitted/false skips camp-point score-band reads. Workers own score calculation and refresh; map reads do not calculate scores. Approximate ZIP/state points and child records are excluded. Overflow rejects the request with HTTP 413 rather than truncating points; omitted or false preserves the existing response. Map and member reads exclude identities with committed source work still queued and overlay those identities from current source rows. Projection state and the live overlay share one repeatable-read snapshot; a proven clean snapshot takes the saved-row fast path. Initial preparation returns HTTP 503 with code=map_cache_warming; callers may retry this state. Successes optionally use application/vnd.schoolsout.map+cbor;version=1 when explicitly requested with Accept at positive quality at least as high as JSON. JSON remains the default; errors before a stream starts remain JSON. The data model and authorization are unchanged; responses vary on Accept and Accept-Encoding and retain private, no-store. Successful finished responses may be reused from a bounded per-process memory cache keyed by normalized options, current disclosure policy, stream version, representation, and compression. PostgreSQL retains validity fences and invalidation history, not response bodies; completed responses do not survive process restarts. Every request still authenticates and checks access. Source, live eligibility, score, geography, and projection changes invalidate dependent responses; reuse expires by its maximum age and the next UTC midnight. Cache failures fall back to normal reads. Private/no-store and JSON error behavior remain unchanged. X-Map-Revision, when available, is an opaque equality token scoped to relevant source changes, disclosure, policy, and UTC day; clients must not parse it. Successful responses carry a weak ETag bound to normalized options, disclosure, representation, and compression. Send it with If-None-Match to revalidate; after current authorization and a fresh source-fence check, a match returns HTTP 304 with no body and without running the map data query. Retain a streamed ETag only after its complete frame passes sequence and count validation. Revision tokens fence server response reuse and remain available to clients; metadata revision changes do not automatically replace an already loaded Explorer point snapshot. Score writes invalidate all map metadata, requests with minimumScore or maximumScore filters, and point requests with includeCampScores=true. The map route additionally accepts application/vnd.schoolsout.map-stream+cbor;version=1. After optional whole-stream gzip decompression, each frame is a four-byte unsigned big-endian payload length followed by one CBOR object, capped at 1 MiB. Frames have type: start (version 1, bounds [west,south,east,north], opaque revision or empty string when unavailable), zero or more points (zero-based sequence, features), result (data contains ordinary map metadata without campPoints), and complete (pointCount, batchCount). Gzip flushes per frame. Points are provisional until complete and matching totals; terminal error frames contain code and message. Abort, overflow, malformed/truncated streams, invalid sequences, and missing completion cannot establish reusable coverage or cached success. Pre-stream errors retain JSON status/error responses. Completed streams may be cached as their own representation. Explorer requests all permitted, plottable camp dots in one world-bounds stream for the current filters/access, with names up front. It retains the completed cohort snapshot until filters/access change, explicit refresh, or reload. Pan and zoom request separate camera-specific counts/groups without downloading points again; metadata revision changes do not automatically refresh the dots. Camp dots open camp information directly at every zoom. Use bounded ordinary JSON/CBOR fallback when streaming is unsupported. Progressive delivery targets earlier points without a fixed latency guarantee. The route also supports application/vnd.schoolsout.map-stream+cbor;version=2 when includeCampPoints=true; omitted or false returns HTTP 400 before streaming. V2 uses the same uint32 big-endian length-prefixed CBOR framing, whole-stream gzip, 1 MiB frame limit, provisional points, terminal errors, and completion checks as v1. Its start frame contains version 2, bounds, opaque revision, and ordered columns: id, longitude, latitude, followed by name when includeCampNames=true and scoreBand when includeCampScores=true. Points frames contain zero-based sequence and positional rows matching those columns; coordinates remain float64 and an included missing or undisclosed name is null. Complete contains matching pointCount and batchCount. V2 has no features or result frame and does not calculate grouping, coverage, viewport, tier, region, or preview metadata. Selected-region validation still returns pre-stream HTTP 400 for a missing boundary or dataset, or a dataset that is not active; it never substitutes an empty successful point population. Accept quality chooses v1 or v2. Each API instance warms completed-response bytes independently, so a restart begins cold. Every reuse validates the current database snapshot, compaction epoch, scoped dependency revision, and UTC evaluation day. Durable canonical map and boundary materialized projections remain in PostgreSQL across restarts. Migration 0047 adds GiST indexes on the guarded WGS84 geography expressions used by selected-region reads from canonical_map_records and entity_configurations. They narrow candidate identities; the query materializes distinct float8 longitude/latitude pairs, evaluates authoritative ST_DWithin once per pair, then joins matches back to every identity. Float8 is the existing ST_MakePoint input conversion, not new rounding. Stored source coordinates, output precision, masking, preview disclosure, boundary geometry, tolerances, and exact membership identities remain unchanged. Prepare migration 0047 before deploying the matching API code. ageYears uses whole completed years. The legacy ageMonths filter remains available; month-shaped aliases derived from canonical inclusive year bounds use [yearMin*12,yearMax*12+11]. collectionId is staff-only; collections contain organizations and do not infer activity or session category labels. Structured filter JSON uses filterKind=organization (default), activity, or session. Matching children select their effectively owning camps, with direct session organization before activity fallback. The cohort includes those camps and all disclosed effectively owned activities/sessions, including children that did not match the tree, before existing legacy filters and per-record geography narrow it using AND. Camp dots use the camp own coordinates. Preserve both parameters across snapshots, viewport reads, members and summaries. Original tree limits, fixed preview disclosure and field permissions apply; the internal camp projection consumes no extra predicate budget. This selection is identical across JSON, CBOR, finite v1/v2 and live SSE transports; global text-search suggestions stay separate.","parameters":[{"description":"West,south,east,north in longitude/latitude degrees. West greater than east crosses the antimeridian.","in":"query","name":"bbox","required":true,"schema":{"example":"-125,24,-66,50","type":"string"}},{"description":"Map zoom used to choose bounded display detail.","in":"query","name":"zoom","required":true,"schema":{"maximum":22,"minimum":0,"type":"number"}},{"description":"Include precise organization points within the requested bounds. Uses the same cohort, selected region, masking, and preview disclosure; rejects overflow rather than sampling.","in":"query","name":"includeCampPoints","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Add disclosed names to campPoints using the existing viewer-level text scrubbing and preview masks. Requires includeCampPoints=true. Hidden/missing names are omitted; omitted/false skips name reads.","in":"query","name":"includeCampNames","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Add persisted organization score bands to campPoints. Requires includeCampPoints=true. Missing prepared scores, restricted accounts, and every preview receive unavailable; omitted/false skips score-band reads.","in":"query","name":"includeCampScores","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Case-insensitive search in each entity kind using its permitted canonical search fields.","explode":true,"in":"query","name":"q","required":false,"schema":{"items":{"maxLength":200,"minLength":1,"type":"string"},"maxItems":5,"minItems":1,"type":"array"},"style":"form"},{"description":"Each record’s own city, case-insensitive exact name or hyphenated slug.","in":"query","name":"city","required":false,"schema":{"example":"seattle","type":"string"}},{"description":"Each record’s own recorded region; distinct from the selected polygon.","in":"query","name":"region","required":false,"schema":{"example":"WA","type":"string"}},{"description":"Exact category recorded on each matching record; categories are independent across organizations, activities, and sessions.","in":"query","name":"category","required":false,"schema":{"example":"STEM","type":"string"}},{"description":"Legacy compatibility filter for month-based ages. New clients should use whole-year ageYears; when a whole-year range is sent through a legacy month-shaped adapter, inclusive bounds expand as [yearMin*12,yearMax*12+11].","in":"query","name":"ageMonths","required":false,"schema":{"example":120,"maximum":100000,"minimum":0,"type":"integer"}},{"description":"Matches the record’s own stated inclusive configuration range.","in":"query","name":"grade","required":false,"schema":{"example":5,"maximum":12,"minimum":-1,"type":"integer"}},{"description":"Inclusive minimum School’s Out score from the persisted ranking snapshot.","in":"query","name":"minimumScore","required":false,"schema":{"example":81,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Inclusive maximum School’s Out score from the persisted ranking snapshot. It must not be below minimumScore.","in":"query","name":"maximumScore","required":false,"schema":{"example":100,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Organization identity or child lineage.","in":"query","name":"organizationId","required":false,"schema":{"type":"string"}},{"description":"Activity identity or matching ancestor/descendant activity.","in":"query","name":"activityId","required":false,"schema":{"type":"string"}},{"description":"Matching activity skill level.","in":"query","name":"skillLevel","required":false,"schema":{"type":"string"}},{"description":"Matching activity format.","in":"query","name":"format","required":false,"schema":{"type":"string"}},{"description":"Matching activity seasonality.","in":"query","name":"seasonality","required":false,"schema":{"type":"string"}},{"description":"Matching session registration status: open, waitlist or closed (the stored enum), or unknown (NULL); any other value is a 400. Its field access gate applies.","in":"query","name":"registrationStatus","required":false,"schema":{"type":"string","enum":["open","waitlist","closed","unknown"]}},{"description":"Matching sessions ending on or after this date.","in":"query","name":"from","required":false,"schema":{"type":"string"}},{"description":"Matching sessions starting on or before this date.","in":"query","name":"to","required":false,"schema":{"type":"string"}},{"description":"Selected source boundary kind; requires regionCode and regionDataset.","in":"query","name":"regionKind","required":false,"schema":{"enum":["state","zip","metro","county","city","neighborhood"],"type":"string"}},{"description":"Stable source identifier for the selected boundary.","in":"query","name":"regionCode","required":false,"schema":{"type":"string"}},{"description":"Immutable imported dataset ID. Retired installed datasets remain selectable.","in":"query","name":"regionDataset","required":false,"schema":{"type":"string"}},{"description":"Revalidate a completed response using its ETag. Current authorization and source dependencies are checked before an unchanged response returns 304.","in":"header","name":"If-None-Match","required":false,"schema":{"type":"string"}},{"description":"Returns records whose stated whole completed age range in inclusive age_range_years bounds includes this age in years.","in":"query","name":"ageYears","required":false,"schema":{"example":10,"maximum":8333,"minimum":0,"type":"integer"}},{"description":"Requires a direct staff or ADMIN account; service delegation, machine keys, and previews are refused. Filters organizations by one shared camp collection; map routes include coherently owned descendants.","in":"query","name":"collectionId","required":false,"schema":{"format":"uuid","type":"string"}},{"name":"filter","in":"query","required":false,"description":"One URL-encoded JSON CanonicalFilter tree, sent once and evaluated against filterKind (organization by default). A matching activity/session selects its effectively owning camp; a session direct organization wins before activity fallback. The resulting cohort includes selected camps and all their disclosed effectively owned children, including nonmatching children, before legacy filters and geography narrow it using AND. Camp dots use each camp own coordinates. Original tree limits and current field/preview disclosure apply; the internal camp projection consumes no extra depth or byte budget. Send the same filter and filterKind on map points, metadata, members and summary reads.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalFilter"}}}},{"name":"filterKind","in":"query","required":false,"description":"Root entity type for filter. Requires filter and appears once. Organization roots select matching camps; activity/session roots select camps owning matching children. This does not limit mixed metadata, members or summary counts to matching children. Every camp dot retains its own address-grade location.","schema":{"type":"string","enum":["organization","activity","session"],"default":"organization"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"campPoints":{"description":"Present only when includeCampPoints=true. Organization-only points within the exact requested projected-coordinate bounds; excludes approximate locations and children.","properties":{"features":{"items":{"properties":{"geometry":{"properties":{"coordinates":{"items":{"type":"number"},"maxItems":2,"minItems":2,"type":"array"},"type":{"const":"Point"}},"required":["type","coordinates"],"type":"object"},"id":{"description":"Organization UUID; distinct organizations retain distinct IDs at a shared coordinate.","format":"uuid","type":"string"},"properties":{"additionalProperties":false,"properties":{"name":{"description":"Present only when includeCampNames=true and the camp name is disclosed. Existing viewer-level text scrubbing and preview name masks apply; missing/hidden names are omitted without an identifier fallback.","type":"string"},"scoreBand":{"description":"Present only when includeCampScores=true. Uses the persisted organization score and the same bands as score details. Missing prepared scores, restricted accounts, and every preview receive unavailable.","enum":["weak","fair","good","strong","unavailable"],"type":"string"}},"type":"object"},"type":{"const":"Feature"}},"required":["type","id","geometry","properties"],"type":"object"},"maxItems":100000,"type":"array"},"type":{"const":"FeatureCollection"}},"required":["type","features"],"type":"object"},"coverage":{"properties":{"approximate":{"minimum":0,"type":"integer"},"byKind":{"additionalProperties":{"properties":{"approximate":{"minimum":0,"type":"integer"},"exact":{"minimum":0,"type":"integer"},"located":{"minimum":0,"type":"integer"},"total":{"minimum":0,"type":"integer"},"unlocated":{"minimum":0,"type":"integer"}},"required":["total","located","exact","approximate","unlocated"],"type":"object"},"type":"object"},"exact":{"minimum":0,"type":"integer"},"located":{"minimum":0,"type":"integer"},"total":{"minimum":0,"type":"integer"},"unlocated":{"minimum":0,"type":"integer"}},"type":"object"},"features":{"items":{"properties":{"geometry":{"properties":{"coordinates":{"items":{"type":"number"},"maxItems":2,"minItems":2,"type":"array"},"type":{"const":"Point"}},"type":"object"},"id":{"type":"string"},"properties":{"properties":{"approximate":{"type":"integer"},"coordinatePrecision":{"$ref":"#/components/schemas/CoordinatePrecision"},"count":{"minimum":1,"type":"integer"},"counts":{"properties":{"activity":{"minimum":0,"type":"integer"},"organization":{"minimum":0,"type":"integer"},"session":{"minimum":0,"type":"integer"}},"required":["organization","activity","session"],"type":"object"},"exact":{"type":"integer"},"featureType":{"enum":["point","cluster"]},"groupId":{"type":"string"},"name":{"type":["string","null"]},"recordId":{"format":"uuid","type":"string"},"targetKind":{"enum":["organization","activity","session"]}},"type":"object"},"type":{"const":"Feature"}},"required":["type","id","geometry","properties"],"type":"object"},"type":"array"},"grouping":{"properties":{"cellDegrees":{"type":"number"},"maxFeatures":{"type":"integer"},"resolution":{"type":"integer"}},"type":"object"},"pointCoordinates":{"description":"Current responses use source for full stored precision of disclosed coordinates. rounded_to_two_decimal_places remains readable in historical saved metadata. Source quality is described separately.","enum":["source","rounded_to_two_decimal_places"]},"preview":{"properties":{"pageSize":{"type":"integer"},"sample":{"const":true}},"type":"object"},"region":{"type":"object"},"tier":{"type":"string"},"type":{"const":"FeatureCollection"},"viewport":{"properties":{"approximate":{"type":"integer"},"bounds":{"items":{"type":"number"},"maxItems":4,"minItems":4,"type":"array"},"count":{"type":"integer"},"counts":{"properties":{"activity":{"minimum":0,"type":"integer"},"organization":{"minimum":0,"type":"integer"},"session":{"minimum":0,"type":"integer"}},"required":["organization","activity","session"],"type":"object"},"exact":{"type":"integer"},"scope":{"const":"intersecting_grid_cells"},"scoreBands":{"description":"Per-kind score-band counts over the same displayed grid cells as counts. Each kind's five bands sum to its count; score disclosure and current scoring versions apply independently per kind.","type":"object","properties":{"organization":{"$ref":"#/components/schemas/MapScoreBandCounts"},"activity":{"$ref":"#/components/schemas/MapScoreBandCounts"},"session":{"$ref":"#/components/schemas/MapScoreBandCounts"}},"required":["organization","activity","session"]}},"type":"object","required":["scoreBands"]}},"required":["type","features","coverage","viewport","grouping","pointCoordinates"],"type":"object"},"success":{"const":true}},"required":["success","data"],"type":"object"}},"application/vnd.schoolsout.map+cbor;version=1":{"schema":{"properties":{"data":{"properties":{"campPoints":{"description":"Present only when includeCampPoints=true. Organization-only points within the exact requested projected-coordinate bounds; excludes approximate locations and children.","properties":{"features":{"items":{"properties":{"geometry":{"properties":{"coordinates":{"items":{"type":"number"},"maxItems":2,"minItems":2,"type":"array"},"type":{"const":"Point"}},"required":["type","coordinates"],"type":"object"},"id":{"description":"Organization UUID; distinct organizations retain distinct IDs at a shared coordinate.","format":"uuid","type":"string"},"properties":{"additionalProperties":false,"properties":{"name":{"description":"Present only when includeCampNames=true and the camp name is disclosed. Existing viewer-level text scrubbing and preview name masks apply; missing/hidden names are omitted without an identifier fallback.","type":"string"},"scoreBand":{"description":"Present only when includeCampScores=true. Uses the persisted organization score and the same bands as score details. Missing prepared scores, restricted accounts, and every preview receive unavailable.","enum":["weak","fair","good","strong","unavailable"],"type":"string"}},"type":"object"},"type":{"const":"Feature"}},"required":["type","id","geometry","properties"],"type":"object"},"maxItems":100000,"type":"array"},"type":{"const":"FeatureCollection"}},"required":["type","features"],"type":"object"},"coverage":{"properties":{"approximate":{"minimum":0,"type":"integer"},"byKind":{"additionalProperties":{"properties":{"approximate":{"minimum":0,"type":"integer"},"exact":{"minimum":0,"type":"integer"},"located":{"minimum":0,"type":"integer"},"total":{"minimum":0,"type":"integer"},"unlocated":{"minimum":0,"type":"integer"}},"required":["total","located","exact","approximate","unlocated"],"type":"object"},"type":"object"},"exact":{"minimum":0,"type":"integer"},"located":{"minimum":0,"type":"integer"},"total":{"minimum":0,"type":"integer"},"unlocated":{"minimum":0,"type":"integer"}},"type":"object"},"features":{"items":{"properties":{"geometry":{"properties":{"coordinates":{"items":{"type":"number"},"maxItems":2,"minItems":2,"type":"array"},"type":{"const":"Point"}},"type":"object"},"id":{"type":"string"},"properties":{"properties":{"approximate":{"type":"integer"},"coordinatePrecision":{"$ref":"#/components/schemas/CoordinatePrecision"},"count":{"minimum":1,"type":"integer"},"counts":{"properties":{"activity":{"minimum":0,"type":"integer"},"organization":{"minimum":0,"type":"integer"},"session":{"minimum":0,"type":"integer"}},"required":["organization","activity","session"],"type":"object"},"exact":{"type":"integer"},"featureType":{"enum":["point","cluster"]},"groupId":{"type":"string"},"name":{"type":["string","null"]},"recordId":{"format":"uuid","type":"string"},"targetKind":{"enum":["organization","activity","session"]}},"type":"object"},"type":{"const":"Feature"}},"required":["type","id","geometry","properties"],"type":"object"},"type":"array"},"grouping":{"properties":{"cellDegrees":{"type":"number"},"maxFeatures":{"type":"integer"},"resolution":{"type":"integer"}},"type":"object"},"pointCoordinates":{"description":"Current responses use source for full stored precision of disclosed coordinates. rounded_to_two_decimal_places remains readable in historical saved metadata. Source quality is described separately.","enum":["source","rounded_to_two_decimal_places"]},"preview":{"properties":{"pageSize":{"type":"integer"},"sample":{"const":true}},"type":"object"},"region":{"type":"object"},"tier":{"type":"string"},"type":{"const":"FeatureCollection"},"viewport":{"properties":{"approximate":{"type":"integer"},"bounds":{"items":{"type":"number"},"maxItems":4,"minItems":4,"type":"array"},"count":{"type":"integer"},"counts":{"properties":{"activity":{"minimum":0,"type":"integer"},"organization":{"minimum":0,"type":"integer"},"session":{"minimum":0,"type":"integer"}},"required":["organization","activity","session"],"type":"object"},"exact":{"type":"integer"},"scope":{"const":"intersecting_grid_cells"},"scoreBands":{"description":"Per-kind score-band counts over the same displayed grid cells as counts. Each kind's five bands sum to its count; score disclosure and current scoring versions apply independently per kind.","type":"object","properties":{"organization":{"$ref":"#/components/schemas/MapScoreBandCounts"},"activity":{"$ref":"#/components/schemas/MapScoreBandCounts"},"session":{"$ref":"#/components/schemas/MapScoreBandCounts"}},"required":["organization","activity","session"]}},"type":"object","required":["scoreBands"]}},"required":["type","features","coverage","viewport","grouping","pointCoordinates"],"type":"object"},"success":{"const":true}},"required":["success","data"],"type":"object"}},"application/vnd.schoolsout.map-stream+cbor;version=1":{"description":"Finite length-prefixed CBOR frames: start, zero or more points, result, complete. Payload length is unsigned uint32 big-endian, at most 1 MiB; optional gzip spans the whole stream. A terminal error replaces successful completion.","schema":{"format":"binary","type":"string"}},"application/vnd.schoolsout.map-stream+cbor;version=2":{"description":"Requires includeCampPoints=true. Finite length-prefixed CBOR frames: start with ordered columns, zero or more points frames with positional rows, then complete. Payload length is unsigned uint32 big-endian, at most 1 MiB; optional gzip spans the whole stream. No map metadata result is calculated. A terminal error replaces successful completion. Selected-region validation failures remain pre-stream HTTP 400 responses.","schema":{"format":"binary","type":"string"}}},"description":"Authorized geographic result.","headers":{"ETag":{"description":"Weak validator bound to query, representation, compression, and disclosure. Retain streamed validators only after successful completion.","schema":{"type":"string"}},"X-Map-Revision":{"description":"Committed dependency revision shared by compatible disclosure and source scopes.","schema":{"type":"string"}}}},"304":{"description":"The completed representation remains current; no response body.","headers":{"ETag":{"schema":{"type":"string"}},"X-Map-Revision":{"schema":{"type":"string"}}}},"400":{"description":"Invalid bounds, filters, cursor, or unavailable selected source boundary. Also returned for malformed, repeated, over-complex or incorrectly typed filter JSON, an unsupported filterKind, or filterKind without filter."},"401":{"description":"Authentication required."},"403":{"description":"Plan, field, or preview capability refuses the read. Structured filter fields and owner relationships must be available under current plan and preview disclosure."},"413":{"description":"Too many camp locations to display. Zoom in or add filters; no partial camp-point collection is returned."},"429":{"description":"Read allowance exceeded."},"503":{"description":"Database map cache is preparing. The response code is map_cache_warming; retry after a short delay."}},"summary":"Browse all canonical entity kinds on a map","tags":["Internal Explorer map"],"x-auth":"Starter or above with current catalog access. Preview requires explorer.browse_masked and preserves the fixed disclosed sample.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/canonical/map\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"bbox=-122.5,47.4,-122.2,47.8\" \\\n  --data-urlencode \"zoom=11\" \\\n  --data-urlencode \"region=WA\"","title":"Read Seattle-area map cells for Washington records"},{"title":"Map camps with a matching session quote and age range","code":"curl --get \"https://apis.schoolsoutapp.com/api/canonical/map\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"bbox=-125,24,-66,50\" \\\n  --data-urlencode \"zoom=3\" \\\n  --data-urlencode \"includeCampPoints=true\" \\\n  --data-urlencode \"filterKind=session\" \\\n  --data-urlencode 'filter={\"all\":[{\"field\":\"age_range_years\",\"op\":\"contains\",\"value\":10},{\"price\":{\"maximum\":400,\"currency\":\"USD\",\"basis\":\"week\"}}]}'"}],"x-internal":true}},"/api/canonical/map/stream":{"get":{"description":"Streams the authorized camp-point snapshot once, then compact point upserts and removals over SSE. Requires includeCampPoints=true and the same bbox, zoom, filters, names, scores, tier and preview disclosure as the finite map route. Each generation begins with event:start containing version=1, mode=snapshot or delta, revision, sourceRevision, bounds, columns, and fromRevision for deltas. Columns are id, longitude, latitude, optionally name, then optionally scoreBand. event:points contains zero-based sequence, rows of positional upsert tuples, and removeIds (empty for snapshots). event:complete contains revision, pointCount, batchCount, upsertCount and removeCount, and is the only event carrying id:revision. Clients commit data and the cursor only after validating the complete generation. First-load batches may be displayed provisionally; replacements and deltas retain the completed map until complete. Send the last committed cursor in Last-Event-ID to resume. A matching current cursor returns event:ready with revision and sourceRevision and no point download; retained history produces a delta; missing or evicted history produces a replacement snapshot. The cursor is query/disclosure scoped and opaque. sourceRevision is the separate equality token used by finite map metadata. Database revision and points are read from the same snapshot. The server checks dependency fences and shares point builds only when dependencies change; idle connections do not query or transmit the full point population repeatedly. Snapshot history is bounded API-process memory, not durable replay; restarts or another replica can require a replacement. Connections are bounded so reconnects renew authorization, and heartbeat comments carry no data changes. Pre-stream failures use ordinary HTTP JSON errors; terminal event:error contains code and message, optionally status. Incomplete generations never advance the cursor. HTTP 401/402/403 or access refusals must discard retained protected data. No worker or canonical write is started by a subscription. Structured filter JSON uses filterKind=organization (default), activity, or session. Matching children select their effectively owning camps, with direct session organization before activity fallback. The cohort includes those camps and all disclosed effectively owned activities/sessions, including children that did not match the tree, before existing legacy filters and per-record geography narrow it using AND. Camp dots use the camp own coordinates. Preserve both parameters across snapshots, viewport reads, members and summaries. Original tree limits, fixed preview disclosure and field permissions apply; the internal camp projection consumes no extra predicate budget. This selection is identical across JSON, CBOR, finite v1/v2 and live SSE transports; global text-search suggestions stay separate.","parameters":[{"description":"West,south,east,north in longitude/latitude degrees. West greater than east crosses the antimeridian.","in":"query","name":"bbox","required":true,"schema":{"example":"-125,24,-66,50","type":"string"}},{"description":"Map zoom used to choose bounded display detail.","in":"query","name":"zoom","required":true,"schema":{"maximum":22,"minimum":0,"type":"number"}},{"description":"Must be true for the camp-point event stream.","in":"query","name":"includeCampPoints","required":true,"schema":{"type":"boolean","const":true}},{"description":"Add disclosed names to campPoints using the existing viewer-level text scrubbing and preview masks. Requires includeCampPoints=true. Hidden/missing names are omitted; omitted/false skips name reads.","in":"query","name":"includeCampNames","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Add persisted organization score bands to campPoints. Requires includeCampPoints=true. Missing prepared scores, restricted accounts, and every preview receive unavailable; omitted/false skips score-band reads.","in":"query","name":"includeCampScores","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Case-insensitive search in each entity kind using its permitted canonical search fields.","explode":true,"in":"query","name":"q","required":false,"schema":{"items":{"maxLength":200,"minLength":1,"type":"string"},"maxItems":5,"minItems":1,"type":"array"},"style":"form"},{"description":"Each record’s own city, case-insensitive exact name or hyphenated slug.","in":"query","name":"city","required":false,"schema":{"example":"seattle","type":"string"}},{"description":"Each record’s own recorded region; distinct from the selected polygon.","in":"query","name":"region","required":false,"schema":{"example":"WA","type":"string"}},{"description":"Exact category recorded on each matching record; categories are independent across organizations, activities, and sessions.","in":"query","name":"category","required":false,"schema":{"example":"STEM","type":"string"}},{"description":"Legacy compatibility filter for month-based ages. New clients should use whole-year ageYears; when a whole-year range is sent through a legacy month-shaped adapter, inclusive bounds expand as [yearMin*12,yearMax*12+11].","in":"query","name":"ageMonths","required":false,"schema":{"example":120,"maximum":100000,"minimum":0,"type":"integer"}},{"description":"Matches the record’s own stated inclusive configuration range.","in":"query","name":"grade","required":false,"schema":{"example":5,"maximum":12,"minimum":-1,"type":"integer"}},{"description":"Inclusive minimum School’s Out score from the persisted ranking snapshot.","in":"query","name":"minimumScore","required":false,"schema":{"example":81,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Inclusive maximum School’s Out score from the persisted ranking snapshot. It must not be below minimumScore.","in":"query","name":"maximumScore","required":false,"schema":{"example":100,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Organization identity or child lineage.","in":"query","name":"organizationId","required":false,"schema":{"type":"string"}},{"description":"Activity identity or matching ancestor/descendant activity.","in":"query","name":"activityId","required":false,"schema":{"type":"string"}},{"description":"Matching activity skill level.","in":"query","name":"skillLevel","required":false,"schema":{"type":"string"}},{"description":"Matching activity format.","in":"query","name":"format","required":false,"schema":{"type":"string"}},{"description":"Matching activity seasonality.","in":"query","name":"seasonality","required":false,"schema":{"type":"string"}},{"description":"Matching session registration status: open, waitlist or closed (the stored enum), or unknown (NULL); any other value is a 400. Its field access gate applies.","in":"query","name":"registrationStatus","required":false,"schema":{"type":"string","enum":["open","waitlist","closed","unknown"]}},{"description":"Matching sessions ending on or after this date.","in":"query","name":"from","required":false,"schema":{"type":"string"}},{"description":"Matching sessions starting on or before this date.","in":"query","name":"to","required":false,"schema":{"type":"string"}},{"description":"Selected source boundary kind; requires regionCode and regionDataset.","in":"query","name":"regionKind","required":false,"schema":{"enum":["state","zip","metro","county","city","neighborhood"],"type":"string"}},{"description":"Stable source identifier for the selected boundary.","in":"query","name":"regionCode","required":false,"schema":{"type":"string"}},{"description":"Immutable imported dataset ID. Retired installed datasets remain selectable.","in":"query","name":"regionDataset","required":false,"schema":{"type":"string"}},{"description":"Returns records whose stated whole completed age range in inclusive age_range_years bounds includes this age in years.","in":"query","name":"ageYears","required":false,"schema":{"example":10,"maximum":8333,"minimum":0,"type":"integer"}},{"description":"Requires a direct staff or ADMIN account; service delegation, machine keys, and previews are refused. Filters organizations by one shared camp collection; map routes include coherently owned descendants.","in":"query","name":"collectionId","required":false,"schema":{"format":"uuid","type":"string"}},{"name":"Last-Event-ID","in":"header","required":false,"description":"The last fully validated and committed stream revision. Partial generations must not advance this cursor. Missing retained history causes a replacement snapshot.","schema":{"type":"string","maxLength":128}},{"name":"filter","in":"query","required":false,"description":"One URL-encoded JSON CanonicalFilter tree, sent once and evaluated against filterKind (organization by default). A matching activity/session selects its effectively owning camp; a session direct organization wins before activity fallback. The resulting cohort includes selected camps and all their disclosed effectively owned children, including nonmatching children, before legacy filters and geography narrow it using AND. Camp dots use each camp own coordinates. Original tree limits and current field/preview disclosure apply; the internal camp projection consumes no extra depth or byte budget. Send the same filter and filterKind on map points, metadata, members and summary reads.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalFilter"}}}},{"name":"filterKind","in":"query","required":false,"description":"Root entity type for filter. Requires filter and appears once. Organization roots select matching camps; activity/session roots select camps owning matching children. This does not limit mixed metadata, members or summary counts to matching children. Every camp dot retains its own address-grade location.","schema":{"type":"string","enum":["organization","activity","session"],"default":"organization"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"SSE snapshot and delta generations, unchanged ready events, and heartbeat comments.","content":{"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"Invalid bounds, filters, cursor, or unavailable selected source boundary. Also returned for malformed, repeated, over-complex or incorrectly typed filter JSON, an unsupported filterKind, or filterKind without filter."},"401":{"description":"Authentication required."},"403":{"description":"Plan, field, or preview capability refuses the read. Structured filter fields and owner relationships must be available under current plan and preview disclosure."},"413":{"description":"Too many camp locations to display. Zoom in or add filters; no partial camp-point collection is returned."},"429":{"description":"Read allowance exceeded."},"503":{"description":"Database map cache is preparing. The response code is map_cache_warming; retry after a short delay."}},"summary":"Stream live camp-point additions, changes and removals","tags":["Internal Explorer map"],"x-auth":"Starter or above with current catalog access. Preview requires explorer.browse_masked and preserves the fixed disclosed sample.","x-curl-examples":[{"title":"Subscribe to live camp points","code":"curl -N --get \"https://apis.schoolsoutapp.com/api/canonical/map/stream\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"bbox=-180,-90,180,90\" \\\n  --data-urlencode \"zoom=0\" \\\n  --data-urlencode \"includeCampPoints=true\" \\\n  --data-urlencode \"includeCampNames=true\" \\\n  --data-urlencode \"includeCampScores=true\""}],"x-internal":true}},"/api/canonical/map/boundaries":{"get":{"description":"Returns bounded simplified GeoJSON for display, with immutable datasetId/kind/code identity, bounds, source, vintage, availability, and explicit truncation. Supply bbox, q, code, or point. Point lookup covers the stored source polygon, including edges, independently of viewport, display simplification, and overlay truncation. Overlapping point candidates retain stable code order and existing feature/byte budgets. Selection uses the stored unsimplified geometry, never this display outline. Metro means Census metropolitan statistical area. City outlines distinguish incorporated places from Census-designated places. Neighborhood coverage exists only where authoritative local sources have been imported. Runtime reads never fetch or replace reference datasets. Display geometry and source bounds are prepared in the database outside request processing. Point and region membership retain original source polygons. Pending preparation returns HTTP 503 with code=map_cache_warming; callers may retry this state. Successes optionally use application/vnd.schoolsout.map+cbor;version=1 when explicitly requested with Accept at positive quality at least as high as JSON. JSON remains the default and all errors remain JSON. The data model and authorization are unchanged; responses vary on Accept and Accept-Encoding and retain private, no-store. Successful finished responses may be reused from a bounded per-process memory cache keyed by normalized options, current disclosure policy, representation, and compression; response bodies do not survive a restart. Every request still authenticates and checks access. Source, live eligibility, score, geography, and projection changes invalidate dependent responses; reuse expires by its maximum age and the next UTC midnight. Cache failures fall back to normal reads. Private/no-store and JSON error behavior remain unchanged. X-Map-Revision, when available, is an opaque equality token scoped to relevant source changes, disclosure, policy, and UTC day; clients must not parse it. Successful responses carry a weak ETag bound to normalized options, disclosure, representation, and compression. Send it with If-None-Match to revalidate; after current authorization and a fresh source-fence check, a match returns HTTP 304 with no body and without running the map data query. Retain a streamed ETag only after its complete frame passes sequence and count validation. Revision tokens fence server response reuse and remain available to clients; metadata revision changes do not automatically replace an already loaded Explorer point snapshot. Score writes invalidate requests with minimumScore or maximumScore filters and requests with includeCampScores=true. Migration 0048 indexes boundary provenance lookup without limiting provenance values. Rows whose combined UTF-8 octet length across dataset_id, kind, source, source_url, and vintage is at most 2,000 bytes use a covering btree index on all five keys; larger rows use a complementary partial btree index on dataset_id and kind. MapBoundaries combines those disjoint bounded and overflow paths with UNION ALL, then retains the existing exact distinct source tuples and ordering. The threshold only routes index use; provenance values are never rejected or truncated. Apply prepare migrations 0047 then 0048 before deploying the matching API code.","parameters":[{"description":"Boundary layer.","in":"query","name":"kind","required":true,"schema":{"enum":["state","zip","metro","county","city","neighborhood"],"type":"string"}},{"description":"West,south,east,north in longitude/latitude degrees. West greater than east crosses the antimeridian.","in":"query","name":"bbox","required":false,"schema":{"example":"-125,24,-66,50","type":"string"}},{"description":"One finite longitude,latitude pair within longitude ±180 and latitude ±85. Exclusive with bbox, code, and q. Returns source polygons covering the point, including edges; zoom controls display detail only.","in":"query","name":"point","required":false,"schema":{"example":"-122.3321,47.6062","type":"string"}},{"description":"Map zoom used to choose bounded display detail.","in":"query","name":"zoom","required":false,"schema":{"maximum":22,"minimum":0,"type":"number"}},{"description":"Installed immutable dataset ID; omitted uses the active snapshot.","in":"query","name":"datasetId","required":false,"schema":{"type":"string"}},{"description":"Search source names/codes instead of a viewport.","in":"query","name":"q","required":false,"schema":{"type":"string"}},{"description":"Return one source boundary and its bounds instead of a viewport.","in":"query","name":"code","required":false,"schema":{"type":"string"}},{"description":"Revalidate a completed response using its ETag. Current authorization and source dependencies are checked before an unchanged response returns 304.","in":"header","name":"If-None-Match","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"availability":{"type":"object"},"datasetId":{"type":"string"},"datasetVersion":{"type":"string"},"features":{"items":{"type":"object"},"type":"array"},"geometryRole":{"const":"display"},"selectionGeometry":{"const":"source"},"truncated":{"type":"boolean"},"type":{"const":"FeatureCollection"}},"required":["type","features","availability","truncated","geometryRole","selectionGeometry"],"type":"object"},"success":{"const":true}},"required":["success","data"],"type":"object"}},"application/vnd.schoolsout.map+cbor;version=1":{"schema":{"properties":{"data":{"properties":{"availability":{"type":"object"},"datasetId":{"type":"string"},"datasetVersion":{"type":"string"},"features":{"items":{"type":"object"},"type":"array"},"geometryRole":{"const":"display"},"selectionGeometry":{"const":"source"},"truncated":{"type":"boolean"},"type":{"const":"FeatureCollection"}},"required":["type","features","availability","truncated","geometryRole","selectionGeometry"],"type":"object"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Authorized geographic result.","headers":{"ETag":{"description":"Weak validator bound to query, representation, compression, and disclosure. Retain streamed validators only after successful completion.","schema":{"type":"string"}},"X-Map-Revision":{"description":"Committed dependency revision shared by compatible disclosure and source scopes.","schema":{"type":"string"}}}},"304":{"description":"The completed representation remains current; no response body.","headers":{"ETag":{"schema":{"type":"string"}},"X-Map-Revision":{"schema":{"type":"string"}}}},"400":{"description":"Invalid bounds, filters, cursor, or unavailable selected source boundary."},"401":{"description":"Authentication required."},"403":{"description":"Plan, field, or preview capability refuses the read."},"429":{"description":"Read allowance exceeded."},"503":{"description":"Database map cache is preparing. The response code is map_cache_warming; retry after a short delay."}},"summary":"Browse, search, or resolve imported region outlines","tags":["Internal Explorer map"],"x-auth":"Starter or above with current catalog access. Preview requires explorer.browse_masked and preserves the fixed disclosed sample.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/canonical/map/boundaries\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"kind=city\" \\\n  --data-urlencode \"point=-122.3321,47.6062\" \\\n  --data-urlencode \"zoom=11\"","title":"Resolve the city outlines covering a point"}],"x-internal":true}},"/api/canonical/map/members":{"get":{"description":"Use the map response groupId and exactly the same catalog and region filters. Complete cell membership is paginated by typed record identity; co-located entries remain accessible at maximum zoom. Preview pagination never escapes its fixed disclosed population. Reads the same durable map projection and masking rules as the map. Initial preparation returns HTTP 503 with code=map_cache_warming. Successes optionally use application/vnd.schoolsout.map+cbor;version=1 when explicitly requested with Accept at positive quality at least as high as JSON. JSON remains the default and all errors remain JSON. The data model and authorization are unchanged; responses vary on Accept and Accept-Encoding and retain private, no-store. Successful finished responses may be reused from a bounded per-process memory cache keyed by normalized options, current disclosure policy, representation, and compression; response bodies do not survive a restart. Every request still authenticates and checks access. Source, live eligibility, score, geography, and projection changes invalidate dependent responses; reuse expires by its maximum age and the next UTC midnight. Cache failures fall back to normal reads. Private/no-store and JSON error behavior remain unchanged. X-Map-Revision, when available, is an opaque equality token scoped to relevant source changes, disclosure, policy, and UTC day; clients must not parse it. Successful responses carry a weak ETag bound to normalized options, disclosure, representation, and compression. Send it with If-None-Match to revalidate; after current authorization and a fresh source-fence check, a match returns HTTP 304 with no body and without running the map data query. Retain a streamed ETag only after its complete frame passes sequence and count validation. Revision tokens fence server response reuse and remain available to clients; metadata revision changes do not automatically replace an already loaded Explorer point snapshot. Score writes invalidate requests with minimumScore or maximumScore filters and requests with includeCampScores=true. Map and member reads exclude identities with committed source work still queued and overlay those identities from current source rows. Projection state and the live overlay share one repeatable-read snapshot; a proven clean snapshot takes the saved-row fast path. ageYears uses whole completed years. The legacy ageMonths filter remains available; month-shaped aliases derived from canonical inclusive year bounds use [yearMin*12,yearMax*12+11]. collectionId is staff-only; collections contain organizations and do not infer activity or session category labels. Structured filter JSON uses filterKind=organization (default), activity, or session. Matching children select their effectively owning camps, with direct session organization before activity fallback. The cohort includes those camps and all disclosed effectively owned activities/sessions, including children that did not match the tree, before existing legacy filters and per-record geography narrow it using AND. Camp dots use the camp own coordinates. Preserve both parameters across snapshots, viewport reads, members and summaries. Original tree limits, fixed preview disclosure and field permissions apply; the internal camp projection consumes no extra predicate budget. This selection is identical across JSON, CBOR, finite v1/v2 and live SSE transports; global text-search suggestions stay separate.","parameters":[{"description":"groupId returned by the map.","in":"query","name":"group","required":true,"schema":{"type":"string"}},{"description":"Member page size: a whole number from 1 to 100, default 100, given once; any other value answers 400.","in":"query","name":"limit","required":false,"schema":{"maximum":100,"minimum":1,"type":"integer"}},{"description":"nextCursor returned by the previous page.","in":"query","name":"after","required":false,"schema":{"type":"string"}},{"description":"Case-insensitive search in each entity kind using its permitted canonical search fields.","explode":true,"in":"query","name":"q","required":false,"schema":{"items":{"maxLength":200,"minLength":1,"type":"string"},"maxItems":5,"minItems":1,"type":"array"},"style":"form"},{"description":"Each record’s own city, case-insensitive exact name or hyphenated slug.","in":"query","name":"city","required":false,"schema":{"example":"seattle","type":"string"}},{"description":"Each record’s own recorded region; distinct from the selected polygon.","in":"query","name":"region","required":false,"schema":{"example":"WA","type":"string"}},{"description":"Exact category recorded on each matching record; categories are independent across organizations, activities, and sessions.","in":"query","name":"category","required":false,"schema":{"example":"STEM","type":"string"}},{"description":"Legacy compatibility filter for month-based ages. New clients should use whole-year ageYears; when a whole-year range is sent through a legacy month-shaped adapter, inclusive bounds expand as [yearMin*12,yearMax*12+11].","in":"query","name":"ageMonths","required":false,"schema":{"example":120,"maximum":100000,"minimum":0,"type":"integer"}},{"description":"Matches the record’s own stated inclusive configuration range.","in":"query","name":"grade","required":false,"schema":{"example":5,"maximum":12,"minimum":-1,"type":"integer"}},{"description":"Inclusive minimum School’s Out score from the persisted ranking snapshot.","in":"query","name":"minimumScore","required":false,"schema":{"example":81,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Inclusive maximum School’s Out score from the persisted ranking snapshot. It must not be below minimumScore.","in":"query","name":"maximumScore","required":false,"schema":{"example":100,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Organization identity or child lineage.","in":"query","name":"organizationId","required":false,"schema":{"type":"string"}},{"description":"Activity identity or matching ancestor/descendant activity.","in":"query","name":"activityId","required":false,"schema":{"type":"string"}},{"description":"Matching activity skill level.","in":"query","name":"skillLevel","required":false,"schema":{"type":"string"}},{"description":"Matching activity format.","in":"query","name":"format","required":false,"schema":{"type":"string"}},{"description":"Matching activity seasonality.","in":"query","name":"seasonality","required":false,"schema":{"type":"string"}},{"description":"Matching session registration status: open, waitlist or closed (the stored enum), or unknown (NULL); any other value is a 400. Its field access gate applies.","in":"query","name":"registrationStatus","required":false,"schema":{"type":"string","enum":["open","waitlist","closed","unknown"]}},{"description":"Matching sessions ending on or after this date.","in":"query","name":"from","required":false,"schema":{"type":"string"}},{"description":"Matching sessions starting on or before this date.","in":"query","name":"to","required":false,"schema":{"type":"string"}},{"description":"Selected source boundary kind; requires regionCode and regionDataset.","in":"query","name":"regionKind","required":false,"schema":{"enum":["state","zip","metro","county","city","neighborhood"],"type":"string"}},{"description":"Stable source identifier for the selected boundary.","in":"query","name":"regionCode","required":false,"schema":{"type":"string"}},{"description":"Immutable imported dataset ID. Retired installed datasets remain selectable.","in":"query","name":"regionDataset","required":false,"schema":{"type":"string"}},{"description":"Revalidate a completed response using its ETag. Current authorization and source dependencies are checked before an unchanged response returns 304.","in":"header","name":"If-None-Match","required":false,"schema":{"type":"string"}},{"description":"Returns records whose stated whole completed age range in inclusive age_range_years bounds includes this age in years.","in":"query","name":"ageYears","required":false,"schema":{"example":10,"maximum":8333,"minimum":0,"type":"integer"}},{"description":"Requires a direct staff or ADMIN account; service delegation, machine keys, and previews are refused. Filters organizations by one shared camp collection; map routes include coherently owned descendants.","in":"query","name":"collectionId","required":false,"schema":{"format":"uuid","type":"string"}},{"name":"filter","in":"query","required":false,"description":"One URL-encoded JSON CanonicalFilter tree, sent once and evaluated against filterKind (organization by default). A matching activity/session selects its effectively owning camp; a session direct organization wins before activity fallback. The resulting cohort includes selected camps and all their disclosed effectively owned children, including nonmatching children, before legacy filters and geography narrow it using AND. Camp dots use each camp own coordinates. Original tree limits and current field/preview disclosure apply; the internal camp projection consumes no extra depth or byte budget. Send the same filter and filterKind on map points, metadata, members and summary reads.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalFilter"}}}},{"name":"filterKind","in":"query","required":false,"description":"Root entity type for filter. Requires filter and appears once. Organization roots select matching camps; activity/session roots select camps owning matching children. This does not limit mixed metadata, members or summary counts to matching children. Every camp dot retains its own address-grade location.","schema":{"type":"string","enum":["organization","activity","session"],"default":"organization"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"groupId":{"type":"string"},"members":{"items":{"properties":{"coordinatePrecision":{"$ref":"#/components/schemas/CoordinatePrecision"},"id":{"format":"uuid","type":"string"},"name":{"type":["string","null"]},"targetKind":{"enum":["organization","activity","session"]},"removed":{"type":"boolean","readOnly":true,"description":"Present only with authorized showRemoved=true. True also includes records beneath a removed ancestor."}},"required":["targetKind","id","name","coordinatePrecision"],"type":"object"},"type":"array"},"nextCursor":{"type":["string","null"]},"total":{"type":"integer"}},"required":["members","total","nextCursor","groupId"],"type":"object"},"success":{"const":true}},"required":["success","data"],"type":"object"}},"application/vnd.schoolsout.map+cbor;version=1":{"schema":{"properties":{"data":{"properties":{"groupId":{"type":"string"},"members":{"items":{"properties":{"coordinatePrecision":{"$ref":"#/components/schemas/CoordinatePrecision"},"id":{"format":"uuid","type":"string"},"name":{"type":["string","null"]},"targetKind":{"enum":["organization","activity","session"]},"removed":{"type":"boolean","readOnly":true,"description":"Present only with authorized showRemoved=true. True also includes records beneath a removed ancestor."}},"required":["targetKind","id","name","coordinatePrecision"],"type":"object"},"type":"array"},"nextCursor":{"type":["string","null"]},"total":{"type":"integer"}},"required":["members","total","nextCursor","groupId"],"type":"object"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Authorized geographic result.","headers":{"ETag":{"description":"Weak validator bound to query, representation, compression, and disclosure. Retain streamed validators only after successful completion.","schema":{"type":"string"}},"X-Map-Revision":{"description":"Committed dependency revision shared by compatible disclosure and source scopes.","schema":{"type":"string"}}}},"304":{"description":"The completed representation remains current; no response body.","headers":{"ETag":{"schema":{"type":"string"}},"X-Map-Revision":{"schema":{"type":"string"}}}},"400":{"description":"Invalid bounds, filters, cursor, or unavailable selected source boundary. Also returned for malformed, repeated, over-complex or incorrectly typed filter JSON, an unsupported filterKind, or filterKind without filter."},"401":{"description":"Authentication required."},"403":{"description":"Plan, field, or preview capability refuses the read. Structured filter fields and owner relationships must be available under current plan and preview disclosure."},"429":{"description":"Read allowance exceeded."},"503":{"description":"Database map cache is preparing. The response code is map_cache_warming; retry after a short delay."}},"summary":"Read the records in one map group","tags":["Internal Explorer map"],"x-auth":"Starter or above with current catalog access. Preview requires explorer.browse_masked and preserves the fixed disclosed sample.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/canonical/map/members\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"group=$MAP_GROUP_ID\" \\\n  --data-urlencode \"region=WA\" \\\n  --data-urlencode \"limit=50\"","title":"Read the records in a map group returned with the same filters"}],"x-internal":true}},"/api/canonical/map/summary":{"get":{"description":"Uses the same masked, filtered mixed-entity cohort and unsimplified selected-region boundary as the map. Region counts are independent of bbox, zoom, and pagination. Paid data access is required; investor preview is not supported. Accessible coordinates use full stored precision on every plan before membership; source quality still limits geographic accuracy. Selected-region membership uses distance in meters from the viewer-masked stored point to the unsimplified boundary, with inclusive layer tolerances initially 100 m for neighborhoods, 500 m for cities/ZIPs, 2 km for counties, 5 km for metros, and 10 km for states. A point-centered tolerance circle may partially overlap the region; the center need not be inside, and records may match multiple regions. The same rule applies to address-grade and ZIP/state approximate points, without source-area polygon intersections or zoom dependence. Unknown, missing, masked, and invalid locations are excluded. Approximate quality remains separate. This does not change structured canonical/Ask location classifications. Category buckets count distinct records and may overlap. Activity age bands overlap; missing ages are reported separately. Upcoming sessions start on or after the UTC query date. Category and week distributions are bounded and expose totalBuckets so clients can disclose omitted buckets. asOf records the original evaluation time, retained on cache reuse, not when source facts were verified. A missing or invalid region is an error, never a successful zero summary. Accepts the same cohort filters as the map: q, category, city, region, ageMonths, grade, organizationId, activityId, skillLevel, format, seasonality, from, to, registrationStatus, minimumScore, and maximumScore. No research is generated or saved. Successes optionally use application/vnd.schoolsout.map+cbor;version=1 when explicitly requested with Accept at positive quality at least as high as JSON. JSON remains the default and all errors remain JSON. The data model and authorization are unchanged; responses vary on Accept and Accept-Encoding and retain private, no-store. Successful finished responses may be reused from a bounded per-process memory cache keyed by normalized options, current disclosure policy, representation, and compression; response bodies do not survive a restart. Every request still authenticates and checks access. Source, live eligibility, score, geography, and projection changes invalidate dependent responses; reuse expires by its maximum age and the next UTC midnight. Cache failures fall back to normal reads. Private/no-store and JSON error behavior remain unchanged. A reused summary preserves its original asOf evaluation timestamp; asOf is not request time or verification time. X-Map-Revision, when available, is an opaque equality token scoped to relevant source changes, disclosure, policy, and UTC day; clients must not parse it. Successful responses carry a weak ETag bound to normalized options, disclosure, representation, and compression. Send it with If-None-Match to revalidate; after current authorization and a fresh source-fence check, a match returns HTTP 304 with no body and without running the map data query. Retain a streamed ETag only after its complete frame passes sequence and count validation. Revision tokens fence server response reuse and remain available to clients; metadata revision changes do not automatically replace an already loaded Explorer point snapshot. Score writes invalidate requests with minimumScore or maximumScore filters and requests with includeCampScores=true. Map and member reads exclude identities with committed source work still queued and overlay those identities from current source rows. Projection state and the live overlay share one repeatable-read snapshot; a proven clean snapshot takes the saved-row fast path. collectionId is staff-only; collections contain organizations and do not infer activity or session category labels. Structured filter JSON uses filterKind=organization (default), activity, or session. Matching children select their effectively owning camps, with direct session organization before activity fallback. The cohort includes those camps and all disclosed effectively owned activities/sessions, including children that did not match the tree, before existing legacy filters and per-record geography narrow it using AND. Camp dots use the camp own coordinates. Preserve both parameters across snapshots, viewport reads, members and summaries. Original tree limits, fixed preview disclosure and field permissions apply; the internal camp projection consumes no extra predicate budget. This selection is identical across JSON, CBOR, finite v1/v2 and live SSE transports; global text-search suggestions stay separate.","parameters":[{"description":"Selected geography kind: state, ZIP, metro, county, city, or neighborhood.","in":"query","name":"regionKind","required":true,"schema":{"type":"string"}},{"description":"Code within the selected geography dataset.","in":"query","name":"regionCode","required":true,"schema":{"type":"string"}},{"description":"Immutable imported geography dataset ID. No active-version substitution.","in":"query","name":"regionDataset","required":true,"schema":{"type":"string"}},{"description":"Revalidate a completed response using its ETag. Current authorization and source dependencies are checked before an unchanged response returns 304.","in":"header","name":"If-None-Match","required":false,"schema":{"type":"string"}},{"description":"Requires a direct staff or ADMIN account; service delegation, machine keys, and previews are refused. Filters organizations by one shared camp collection; map routes include coherently owned descendants.","in":"query","name":"collectionId","required":false,"schema":{"format":"uuid","type":"string"}},{"description":"Returns organizations whose stated whole completed age range in inclusive age_range_years bounds includes this age in years.","in":"query","name":"ageYears","required":false,"schema":{"example":10,"maximum":8333,"minimum":0,"type":"integer"}},{"description":"Legacy compatibility filter for month-based ages. New clients should use whole-year ageYears; when a whole-year range is sent through a legacy month-shaped adapter, inclusive bounds expand as [yearMin*12,yearMax*12+11].","in":"query","name":"ageMonths","required":false,"schema":{"example":120,"maximum":100000,"minimum":0,"type":"integer"}},{"name":"filter","in":"query","required":false,"description":"One URL-encoded JSON CanonicalFilter tree, sent once and evaluated against filterKind (organization by default). A matching activity/session selects its effectively owning camp; a session direct organization wins before activity fallback. The resulting cohort includes selected camps and all their disclosed effectively owned children, including nonmatching children, before legacy filters and geography narrow it using AND. Camp dots use each camp own coordinates. Original tree limits and current field/preview disclosure apply; the internal camp projection consumes no extra depth or byte budget. Send the same filter and filterKind on map points, metadata, members and summary reads.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalFilter"}}}},{"name":"filterKind","in":"query","required":false,"description":"Root entity type for filter. Requires filter and appears once. Organization roots select matching camps; activity/session roots select camps owning matching children. This does not limit mixed metadata, members or summary counts to matching children. Every camp dot retains its own address-grade location.","schema":{"type":"string","enum":["organization","activity","session"],"default":"organization"}},{"description":"Case-insensitive search in each entity kind using its permitted canonical search fields.","explode":true,"in":"query","name":"q","required":false,"schema":{"items":{"maxLength":200,"minLength":1,"type":"string"},"maxItems":5,"minItems":1,"type":"array"},"style":"form"},{"description":"Each record’s own city, case-insensitive exact name or hyphenated slug.","in":"query","name":"city","required":false,"schema":{"example":"seattle","type":"string"}},{"description":"Each record’s own recorded region; distinct from the selected polygon.","in":"query","name":"region","required":false,"schema":{"example":"WA","type":"string"}},{"description":"Exact category recorded on each matching record; categories are independent across organizations, activities, and sessions.","in":"query","name":"category","required":false,"schema":{"example":"STEM","type":"string"}},{"description":"Matches the record’s own stated inclusive configuration range.","in":"query","name":"grade","required":false,"schema":{"example":5,"maximum":12,"minimum":-1,"type":"integer"}},{"description":"Inclusive minimum School’s Out score from the persisted ranking snapshot.","in":"query","name":"minimumScore","required":false,"schema":{"example":81,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Inclusive maximum School’s Out score from the persisted ranking snapshot. It must not be below minimumScore.","in":"query","name":"maximumScore","required":false,"schema":{"example":100,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Organization identity or child lineage.","in":"query","name":"organizationId","required":false,"schema":{"type":"string"}},{"description":"Activity identity or matching ancestor/descendant activity.","in":"query","name":"activityId","required":false,"schema":{"type":"string"}},{"description":"Matching activity skill level.","in":"query","name":"skillLevel","required":false,"schema":{"type":"string"}},{"description":"Matching activity format.","in":"query","name":"format","required":false,"schema":{"type":"string"}},{"description":"Matching activity seasonality.","in":"query","name":"seasonality","required":false,"schema":{"type":"string"}},{"description":"Matching session registration status; its field access gate applies.","in":"query","name":"registrationStatus","required":false,"schema":{"type":"string"}},{"description":"Matching sessions ending on or after this date.","in":"query","name":"from","required":false,"schema":{"type":"string"}},{"description":"Matching sessions starting on or before this date.","in":"query","name":"to","required":false,"schema":{"type":"string"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Summary envelope: success true and data with region (kind, code, datasetId, name), asOf, and metrics. Metrics include counts and location membership per entity kind; organization/activity category distributions; activityAges; upcomingSessions (total, undated, nextStartDate, weeks); and per-kind missing field counts. Distribution buckets contain value/count and totalBuckets. Empty populations retain zeros and empty arrays.","headers":{"ETag":{"description":"Weak validator bound to query, representation, compression, and disclosure. Retain streamed validators only after successful completion.","schema":{"type":"string"}},"X-Map-Revision":{"description":"Committed dependency revision shared by compatible disclosure and source scopes.","schema":{"type":"string"}}}},"304":{"description":"The completed representation remains current; no response body.","headers":{"ETag":{"schema":{"type":"string"}},"X-Map-Revision":{"schema":{"type":"string"}}}},"400":{"description":"Invalid or missing region selection or filter. Also returned for malformed, repeated, over-complex or incorrectly typed filter JSON, an unsupported filterKind, or filterKind without filter."},"401":{"description":"Authentication required."},"403":{"description":"Paid data access required or preview not supported. Structured filter fields and owner relationships must be available under current plan and preview disclosure."},"503":{"description":"Summary temporarily unavailable."}},"summary":"Summarize a selected Explorer region","tags":["Internal canonical queries"],"x-auth":"Starter or above with current catalog access. Investor preview sessions are refused.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/canonical/map/summary\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"regionKind=city\" \\\n  --data-urlencode \"regionCode=$REGION_CODE\" \\\n  --data-urlencode \"regionDataset=$REGION_DATASET_ID\"","title":"Summarize a region selected from the boundaries response"}],"x-internal":true}},"/api/canonical/reference":{"get":{"description":"Requires current staff or ADMIN operator access and rejects preview sessions. Reads the canonical PostgreSQL catalog in one read-only snapshot and combines it with deployed application definitions, including relational sources for structured values and response-only media galleries and image URLs. Fields may include configuration inheritance metadata describing their canonical unit, eligible parent order, and local writability; physical storage entries remain independent. Documenting response enrichments does not add selectable SQL columns or change public record projections. Private no-store response; generatedAt is metadata read time, not record verification time.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/CanonicalReference"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Live reference with explicit coverage issues when schema and application metadata disagree.","headers":{"Cache-Control":{"description":"private, no-store","schema":{"type":"string"}}}},"401":{"description":"No valid credential."},"403":{"description":"Staff or ADMIN access required; preview sessions are not supported."},"500":{"description":"Live database metadata could not be read; no cached reference is substituted."},"503":{"description":"Reference support is not configured on this API release."}},"summary":"Read the live staff data model reference","tags":["Canonical"],"x-auth":"Current staff or ADMIN operator identity. Preview sessions are rejected.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/canonical/reference\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read the staff data model reference"}],"x-internal":true}},"/api/canonical/search":{"get":{"description":"Without mode, retains the canonical search contract: results in organization/activity/session order and an exact total. mode=suggestions ranks one bounded page globally by exact name, prefix, then other name/place matches, with stable folded-name, kind, and UUID ties. Suggestions match names, related camp/activity names, city, and region; descriptions and notes remain in ordinary search only. Suggestions return no total and accept only q, mode, and limit. Query %, _, and ! are literal in suggestions. Each suggestion includes target_kind, id, name, parent_id, parent_name, city, region, verified, and nullable organization_id. The optional location uses only the linked active nonblacklisted organization saved coordinate pair with full stored precision on every plan and finite latitude ±85/longitude ±180 bounds, plus saved coordinate_precision. Missing precision is unknown accuracy. Child coordinates and map centroids never supply camp navigation. Unavailable camps have no organization_id or location. Preview suggestions match fixed disclosed per-kind SQL populations before filters and resolve related names and camp links only inside disclosed organization/activity samples; field masks prevent matching and coordinate disclosure. Ordinary account search without mode retains its existing contract. Preview ordinary search is refused with 403; preview must use mode=suggestions.","parameters":[{"description":"One to five trimmed, deduplicated terms of at most 200 UTF-16 characters each; any term may match.","explode":true,"in":"query","name":"q","required":true,"schema":{"items":{"maxLength":200,"type":"string"},"maxItems":5,"minItems":1,"type":"array"},"style":"form"},{"description":"Use suggestions for a bounded global ranking and linked camp location. Omit for ordinary canonical search.","in":"query","name":"mode","schema":{"enum":["suggestions"],"type":"string"}},{"description":"Restrict suggestions to one canonical record kind. Valid only with mode=suggestions.","in":"query","name":"kind","schema":{"enum":["organization","activity","session"],"type":"string"}},{"description":"Suggestions: whole number 1–20, default 10. Ordinary search: whole number 1–100, default 30. Given once; any other value answers 400.","in":"query","name":"limit","schema":{"maximum":100,"minimum":1,"type":"integer"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"oneOf":[{"properties":{"mode":{"const":"suggestions","type":"string"},"preview":{"properties":{"pageSize":{"type":"integer"},"sample":{"type":"boolean"}},"type":"object"},"queries":{"items":{"type":"string"},"type":"array"},"resultLimit":{"type":["integer","null"]},"results":{"items":{"properties":{"city":{"type":["string","null"]},"id":{"format":"uuid","type":"string"},"location":{"properties":{"coordinate_precision":{"enum":["exact","building","parcel","street","zip","state","unknown",null],"type":["string","null"]},"latitude":{"maximum":85,"minimum":-85,"type":"number"},"longitude":{"maximum":180,"minimum":-180,"type":"number"}},"required":["latitude","longitude","coordinate_precision"],"type":"object"},"name":{"type":["string","null"]},"organization_name":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"prices":{"type":"array","items":{"type":"object","additionalProperties":true}},"organization_id":{"format":"uuid","type":["string","null"]},"parent_id":{"format":"uuid","type":["string","null"]},"parent_name":{"type":["string","null"]},"region":{"type":["string","null"]},"target_kind":{"enum":["organization","activity","session"],"type":"string"},"verified":{"type":"boolean"},"removed":{"type":"boolean","readOnly":true,"description":"Present only with authorized showRemoved=true."}},"type":"object"},"type":"array"},"sourceClass":{"const":"canonical","type":"string"},"tier":{"type":"string"}},"required":["mode","results"],"type":"object"},{"description":"Ordinary account search, with no mode query parameter.","properties":{"queries":{"items":{"type":"string"},"type":"array"},"resultLimit":{"type":"null"},"results":{"items":{"description":"Existing canonical search projection, grouped by organization, activity, then session.","type":"object","properties":{"after_care_pricing":{"$ref":"#/components/schemas/CarePricing","readOnly":true,"description":"Read-only compatibility quote when every prices option records the same after-care quote. Unknown when any option has unknown or different pricing. Edit the quote in prices."},"before_care_pricing":{"$ref":"#/components/schemas/CarePricing","readOnly":true,"description":"Read-only compatibility quote when every prices option records the same before-care quote. Unknown when any option has unknown or different pricing. Edit the quote in prices."},"registration_close_date":{"description":"Session-owned last date this session accepts registrations; it has no activity or organization fallback.","format":"date","type":"string"},"registration_open_date":{"description":"Session-owned first date this session accepts registrations; it has no activity or organization fallback.","format":"date","type":"string"},"schedule_configurations":{"description":"Authoritative attendance configurations using explicit dates or bounded weekly date_rule values. Omission means no structured schedule is available; empty arrays are invalid. The whole collection is one reversible contribution. Rules remain rules in storage and responses; derived bounds do not imply attendance on intervening dates. Pricing inside configurations follows the same access policy as other prices.","items":{"$ref":"#/components/schemas/SessionScheduleConfiguration"},"minItems":1,"type":"array"},"prices":{"description":"Ordered session enrollment options with optional source-grounded descriptions and before- and after-care quotes. A care-only option leaves tuition unknown. An empty array means no recorded price; the complete collection, including descriptions and care quotes, is one canonical field.","items":{"$ref":"#/components/schemas/SessionPrice"},"type":"array"},"resolved_cover":{"anyOf":[{"$ref":"#/components/schemas/CanonicalResolvedCover"},{"type":"null"}],"description":"Present on permitted ordinary JSON catalog reads, including includeMedia=false; omitted from previews and named aggregate reads."},"removed":{"type":"boolean","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_survivor_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"Present only with authorized showRemoved=true. Optional active same-kind survivor linked from the removed root; no data, descendants, or history move to it."},"removal_batch_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_review_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_review_version":{"type":["integer","null"],"readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."},"removal_reason":{"type":["string","null"],"readOnly":true,"description":"Present only with authorized showRemoved=true; retained removal state and attribution."}}},"type":"array"},"sourceClass":{"const":"canonical","type":"string"},"tier":{"type":"string"},"total":{"minimum":0,"type":"integer"}},"required":["results","total"],"type":"object"}]},"success":{"type":"boolean"}},"type":"object"}}},"description":"Successful canonical search."},"400":{"description":"Missing/empty/overlong query, unsupported mode, or malformed suggestions controls."},"401":{"description":"Invalid or missing credentials."},"402":{"description":"Catalog data access requires Starter or above."},"403":{"description":"Preview disclosure is unavailable, or an ordinary preview search was attempted without mode=suggestions."}},"summary":"Search canonical records globally or suggest map navigation targets","tags":["Internal Explorer map"],"x-auth":"Starter or above with current catalog access; preview suggestions require explorer.browse_masked and fixed disclosed samples.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/canonical/search\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"q=wildwood\" \\\n  --data-urlencode \"mode=suggestions\" \\\n  --data-urlencode \"limit=10\"","title":"Suggest map navigation targets"}],"x-internal":true}},"/api/collections":{"get":{"description":"Lists staff-managed collections. archived=false is the default; archived=true selects archived collections, and archived=all includes both. Counts distinguish retained memberships from currently available camps. Collections contain organizations only.","parameters":[{"in":"query","name":"archived","required":false,"schema":{"default":"false","enum":["true","false","all"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"collections":{"items":{"$ref":"#/components/schemas/Collection"},"type":"array"}},"required":["collections"],"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Active or archived shared camp collections with membership counts."},"401":{"description":"Authentication required."},"403":{"description":"Direct staff/ADMIN account required."}},"security":[{"ApiKeyAuth":[]}],"summary":"List shared camp collections","tags":["Collections"],"x-auth":"Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. Cookie mutations require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/collections\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"archived=true\"","title":"List active and archived collections"}],"x-internal":true},"post":{"description":"Creates a shared staff collection with a case-insensitively unique active name from 1 to 120 characters.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionInput"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Collection"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Created shared collection."},"401":{"description":"Authentication required."},"403":{"description":"Direct staff/ADMIN account required."},"404":{"description":"Collection or camp not found."},"409":{"description":"Name conflict or archived collection."}},"security":[{"ApiKeyAuth":[]}],"summary":"Create a camp collection","tags":["Collections"],"x-auth":"Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. Cookie mutations require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/collections\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Summer research\"}'","title":"Create a shared camp collection"}],"x-internal":true}},"/api/collections/{id}":{"get":{"description":"Reads a collection including archived collections; archived collections preserve membership and remain readable.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Collection"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Collection metadata and member counts."},"401":{"description":"Authentication required."},"403":{"description":"Direct staff/ADMIN account required."},"404":{"description":"Collection or camp not found."},"409":{"description":"Name conflict or archived collection."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read a camp collection","tags":["Collections"],"x-auth":"Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. Cookie mutations require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/collections/$COLLECTION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read a collection"}],"x-internal":true},"patch":{"description":"Supply name to rename an active collection, or archived=true to archive it; visibility, placement, position and cover control whether and where it is featured on Explore (GET /api/featured-collections). Archived collections cannot be changed or restored.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionUpdateInput"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Collection"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Updated collection."},"401":{"description":"Authentication required."},"403":{"description":"Direct staff/ADMIN account required."},"404":{"description":"Collection or camp not found."},"409":{"description":"Name conflict or archived collection."}},"security":[{"ApiKeyAuth":[]}],"summary":"Rename or archive a camp collection","tags":["Collections"],"x-auth":"Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. Cookie mutations require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/collections/$COLLECTION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Summer research 2027\"}'","title":"Rename or archive a collection"}],"x-internal":true}},"/api/collections/{id}/organizations":{"get":{"description":"Returns {organizationIds, unavailableOrganizationIds} from one SQL snapshot. organizationIds contains requested IDs of available camps in the collection. unavailableOrganizationIds contains requested IDs that are missing, deleted, or blacklisted, without distinguishing why. Both arrays omit names. Archived collections are readable. Only absence from organizationIds for an available ID confirms non-membership; an unavailable ID omitted from that array cannot confirm removal.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Comma-separated list of 1–100 distinct organization UUIDs. Inputs are UUID-normalized and deduplicated.","in":"query","name":"organizationIds","required":true,"schema":{"example":"uuid,uuid","maxLength":3699,"minLength":32,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"organizationIds":{"description":"Requested available organization IDs present in the collection.","items":{"format":"uuid","type":"string"},"type":"array"},"unavailableOrganizationIds":{"description":"Requested IDs that are missing, deleted, or blacklisted; the reason is not distinguished.","items":{"format":"uuid","type":"string"},"type":"array"}},"required":["organizationIds","unavailableOrganizationIds"],"type":"object"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Requested available member IDs and requested unavailable IDs from one SQL snapshot."},"400":{"description":"Invalid or empty organizationIds list, more than 100 distinct UUIDs, or malformed UUID."},"401":{"description":"Authentication required."},"403":{"description":"Direct staff/ADMIN account required."},"404":{"description":"Collection not found."}},"security":[{"ApiKeyAuth":[]}],"summary":"Check camp membership in a collection","tags":["Collections"],"x-auth":"Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/collections/$COLLECTION_ID/organizations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"organizationIds=$CAMP_ID_1,$CAMP_ID_2\"","title":"Check selected camps in one collection"}],"x-internal":true}},"/api/collections/{id}/organizations/{organizationId}":{"delete":{"description":"Removes the organization membership. Repeating the request is idempotent.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"organizationId","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"added":{"type":"boolean"},"collectionId":{"format":"uuid","type":"string"},"organizationId":{"format":"uuid","type":"string"},"removed":{"type":"boolean"}},"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Membership result; added or removed is false when membership already had the requested state."},"400":{"description":"Invalid ID or request body."},"401":{"description":"Authentication required."},"403":{"description":"Direct staff/ADMIN account required."},"404":{"description":"Collection or camp not found."},"409":{"description":"Archived collection."}},"security":[{"ApiKeyAuth":[]}],"summary":"Remove a camp from a collection","tags":["Collections"],"x-auth":"Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. Cookie mutations require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/collections/$COLLECTION_ID/organizations/$CAMP_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Remove a camp from a collection"}],"x-internal":true},"put":{"description":"Adds an available, nondeleted, nonblacklisted organization. Repeating the request is idempotent. Archived collections cannot be changed.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"organizationId","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"added":{"type":"boolean"},"collectionId":{"format":"uuid","type":"string"},"organizationId":{"format":"uuid","type":"string"},"removed":{"type":"boolean"}},"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Membership result; added or removed is false when membership already had the requested state."},"400":{"description":"Invalid ID or request body."},"401":{"description":"Authentication required."},"403":{"description":"Direct staff/ADMIN account required."},"404":{"description":"Collection or available camp not found."},"409":{"description":"Archived collection."}},"security":[{"ApiKeyAuth":[]}],"summary":"Add a camp to a collection","tags":["Collections"],"x-auth":"Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. Cookie mutations require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/collections/$COLLECTION_ID/organizations/$CAMP_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Add a camp to a collection"}],"x-internal":true}},"/api/users/viewer/saved":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"List saved items","description":"The Saved list, newest first. Each item lists the viewer's own collections that hold it (`collectionIds`). `counts` count every save, available or not, whatever `kind`, page or `keys` was asked for. `keys` answers \"is this saved?\" for one page of cards: only the saved ones come back, all on one page with a null `nextCursor`, and `cursor` and `limit` are refused with it.","parameters":[{"name":"kind","in":"query","required":false,"schema":{"enum":["activity","organization"]},"description":"Only this kind."},{"name":"keys","in":"query","required":false,"schema":{"type":"string"},"description":"Up to 100 comma-separated item keys."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"nextCursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["items","nextCursor","counts"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/FamilySocialSavedItem"}},"nextCursor":{"type":["string","null"],"description":"Opaque; pass as cursor for the next page. Null on the last page."},"counts":{"type":"object","required":["total","activities","organizations"],"additionalProperties":false,"description":"Every save of the viewer, available or not.","properties":{"total":{"type":"integer","minimum":0},"activities":{"type":"integer","minimum":0},"organizations":{"type":"integer","minimum":0}}}}}}}}}},"400":{"description":"Invalid kind, keys, cursor or limit."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"List saved items","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/saved\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/saved/{itemKey}":{"put":{"tags":["Internal family social"],"x-internal":true,"summary":"Save an item","description":"Idempotent: 201 when saved now, 200 when it already was. Undo sends back the `savedAt` a DELETE returned, accepted only within the undo window, so the item returns to its old position.","parameters":[{"name":"itemKey","in":"path","required":true,"schema":{"type":"string","pattern":"^(activity|organization):[0-9a-f-]{36}$"},"description":"activity:<uuid> or organization:<uuid>, URL-encoded."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"savedAt":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Already saved.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["item"],"properties":{"item":{"$ref":"#/components/schemas/FamilySocialItemSummary"}}}}}}}},"201":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["item"],"properties":{"item":{"$ref":"#/components/schemas/FamilySocialItemSummary"}}}}}}}},"400":{"description":"Invalid key or savedAt."},"401":{"description":"Current account required."},"404":{"description":"Not in the catalog."},"409":{"description":"Saved is full (only while a saved cap is on), or the undo window has passed."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Save an item","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/saved/activity:$ACTIVITY_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'"}]},"delete":{"tags":["Internal family social"],"x-internal":true,"summary":"Unsave an item","description":"Removes the item from Saved and from all of the viewer's collections in one transaction. `restore` is what Undo sends back; it is null when the item was not saved.","parameters":[{"name":"itemKey","in":"path","required":true,"schema":{"type":"string","pattern":"^(activity|organization):[0-9a-f-]{36}$"},"description":"activity:<uuid> or organization:<uuid>, URL-encoded."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["removed","restore"],"properties":{"removed":{"type":"boolean"},"restore":{"oneOf":[{"$ref":"#/components/schemas/FamilySocialRestore"},{"type":"null"}]}}}}}}}},"400":{"description":"Invalid key."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Unsave an item","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/saved/activity:$ACTIVITY_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/saved/{itemKey}/collections":{"put":{"tags":["Internal family social"],"x-internal":true,"summary":"Set an item's collections","description":"The item ends up saved and in exactly these of the viewer's collections.","parameters":[{"name":"itemKey","in":"path","required":true,"schema":{"type":"string","pattern":"^(activity|organization):[0-9a-f-]{36}$"},"description":"activity:<uuid> or organization:<uuid>, URL-encoded."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["collectionIds"],"properties":{"collectionIds":{"type":"array","maxItems":100,"items":{"type":"string","format":"uuid"}}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["item","collectionIds"],"properties":{"item":{"$ref":"#/components/schemas/FamilySocialItemSummary"},"collectionIds":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}}}},"400":{"description":"Invalid key or collectionIds."},"401":{"description":"Current account required."},"404":{"description":"A collection is not the viewer's, or the item is not in the catalog."},"409":{"description":"Saved or a collection is full (only while a cap is on)."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Set an item's collections","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/saved/activity:$ACTIVITY_ID/collections\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"collectionIds\":[\"$COLLECTION_ID\"]}'"}]}},"/api/users/viewer/collections":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"List your and followed collections","description":"`own`: the viewer's collections, most recently changed first. `following`: the collections the viewer follows, most recently followed first. One made private or archived stays as a `hidden` summary, so it can be seen and unfollowed; one whose owner blocked or was blocked by the viewer, or whose owner's account is gone, is left out. Each list is keyset-paged on its own order: `limit` applies to each, and a `cursor` continues the one list `include` names.","parameters":[{"name":"include","in":"query","required":false,"schema":{"type":"string"},"description":"own, following or own,following (default both). With cursor, exactly one."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"ownNextCursor or followingNextCursor from the previous page, with include naming that list."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size of each list."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["own","following","ownNextCursor","followingNextCursor"],"properties":{"own":{"type":"array","items":{"$ref":"#/components/schemas/FamilySocialCollectionSummary"}},"following":{"type":"array","items":{"$ref":"#/components/schemas/FamilySocialCollectionSummary"}},"ownNextCursor":{"type":["string","null"],"description":"Opaque; pass as cursor with include=own for the next page. Null on the last page or when own was not included."},"followingNextCursor":{"type":["string","null"],"description":"Opaque; pass as cursor with include=following for the next page. Null on the last page or when following was not included."}}}}}}}},"400":{"description":"Invalid include, cursor or limit, or a cursor with both lists."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"List your and followed collections","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/collections\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal family social"],"x-internal":true,"summary":"Create a collection","description":"Names are 1–60 characters after trimming, not \"Saved\", unique per parent ignoring case. With `itemKey` the item is saved and added.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":60},"visibility":{"enum":["private","public"],"default":"private"},"itemKey":{"type":"string"}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["collection"],"properties":{"collection":{"$ref":"#/components/schemas/FamilySocialCollectionSummary"}}}}}}}},"400":{"description":"Invalid name, visibility or itemKey."},"401":{"description":"Current account required."},"404":{"description":"itemKey is not in the catalog."},"409":{"description":"Duplicate name, or the collection or saved cap is reached (only while one is on)."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Create a collection","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/collections\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Rainy days\"}'"}]}},"/api/users/viewer/collections/{collectionId}":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"Read a collection","description":"The viewer's own collection, or one they follow, with a page of items newest first. A followed collection made private or archived answers its `hidden` summary with no items. Other private, blocked and missing collections all answer 404.","parameters":[{"name":"collectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"nextCursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["collection"],"properties":{"collection":{"$ref":"#/components/schemas/FamilySocialCollectionDetail"}}}}}}}},"400":{"description":"Invalid cursor or limit."},"401":{"description":"Current account required."},"404":{"description":"Not available to the viewer."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Read a collection","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/collections/$COLLECTION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"patch":{"tags":["Internal family social"],"x-internal":true,"summary":"Rename or change visibility","description":"Owner only. Making a public collection private keeps its follows; their reads answer 404 until it is public again.","parameters":[{"name":"collectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"name":{"type":"string","minLength":1,"maxLength":60},"visibility":{"enum":["private","public"]}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["collection"],"properties":{"collection":{"$ref":"#/components/schemas/FamilySocialCollectionSummary"}}}}}}}},"400":{"description":"Invalid name or visibility."},"401":{"description":"Current account required."},"403":{"description":"Visible to the viewer but someone else's."},"404":{"description":"Not available to the viewer."},"409":{"description":"Duplicate name."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Rename or change visibility","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/collections/$COLLECTION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"visibility\":\"public\"}'"}]},"delete":{"tags":["Internal family social"],"x-internal":true,"summary":"Delete a collection","description":"Owner only. Its items stay in Saved; its follows are removed.","parameters":[{"name":"collectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["deleted"],"properties":{"deleted":{"const":true}}}}}}}},"401":{"description":"Current account required."},"403":{"description":"Someone else's."},"404":{"description":"Not available to the viewer."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Delete a collection","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/collections/$COLLECTION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/collections/{collectionId}/items/{itemKey}":{"put":{"tags":["Internal family social"],"x-internal":true,"summary":"Add an item to a collection","description":"Owner only. Saves the item if needed. The item joins the collection now; its place in the collection is never chosen by the client.","parameters":[{"name":"collectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"itemKey","in":"path","required":true,"schema":{"type":"string","pattern":"^(activity|organization):[0-9a-f-]{36}$"},"description":"activity:<uuid> or organization:<uuid>, URL-encoded."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["collection"],"properties":{"collection":{"$ref":"#/components/schemas/FamilySocialCollectionSummary"}}}}}}}},"400":{"description":"Invalid key, or a body with properties."},"401":{"description":"Current account required."},"403":{"description":"Someone else's."},"404":{"description":"Collection not available, or item not in the catalog."},"409":{"description":"The collection or Saved is full (only while a cap is on)."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Add an item to a collection","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/collections/$COLLECTION_ID/items/activity:$ACTIVITY_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"delete":{"tags":["Internal family social"],"x-internal":true,"summary":"Remove an item from a collection","description":"Owner only. The item stays in Saved.","parameters":[{"name":"collectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"itemKey","in":"path","required":true,"schema":{"type":"string","pattern":"^(activity|organization):[0-9a-f-]{36}$"},"description":"activity:<uuid> or organization:<uuid>, URL-encoded."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["collection"],"properties":{"collection":{"$ref":"#/components/schemas/FamilySocialCollectionSummary"}}}}}}}},"400":{"description":"Invalid key."},"401":{"description":"Current account required."},"403":{"description":"Someone else's."},"404":{"description":"Collection not available."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Remove an item from a collection","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/collections/$COLLECTION_ID/items/activity:$ACTIVITY_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/followed-collections/{collectionId}":{"put":{"tags":["Internal family social"],"x-internal":true,"summary":"Follow a collection","description":"Follows a visible public collection.","parameters":[{"name":"collectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["following"],"properties":{"following":{"type":"boolean"}}}}}}}},"401":{"description":"Current account required."},"404":{"description":"Not visible to the viewer."},"409":{"description":"Your own collection, or the follow cap is reached (only while it is on)."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Follow a collection","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/followed-collections/$COLLECTION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"delete":{"tags":["Internal family social"],"x-internal":true,"summary":"Unfollow a collection","description":"Always allowed, including for a collection that went private.","parameters":[{"name":"collectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["following"],"properties":{"following":{"type":"boolean"}}}}}}}},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Unfollow a collection","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/followed-collections/$COLLECTION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/shared-collections/{collectionId}":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"Open a shared collection","description":"A public collection by link, with a page of its available items. Private (except to its owner), archived and missing collections all answer 404.","parameters":[{"name":"collectionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"nextCursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["collection"],"properties":{"collection":{"$ref":"#/components/schemas/FamilySocialCollectionDetail"}}}}}}}},"400":{"description":"Invalid cursor or limit."},"404":{"description":"Not available."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Public. A signed-in parent (cookie or account credential) is optional: with one, `following` and saved times are filled, blocked owners answer 404, and the response is `private, no-store`; without one it is `public, max-age=60`. Machine keys and delegated service requests read as signed out.","x-curl-examples":[{"title":"Open a shared collection","code":"curl \"https://apis.schoolsoutapp.com/api/shared-collections/$COLLECTION_ID\""}]}},"/api/featured-collections":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"List featured shelves","description":"Public staff and organization collections placed on a shelf, in position order, each with its first 12 available items.","parameters":[{"name":"placement","in":"query","required":false,"schema":{"enum":["explore","businesses"],"default":"explore"},"description":"The shelf."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":20,"default":20},"description":"How many collections."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["collections"],"properties":{"collections":{"type":"array","items":{"$ref":"#/components/schemas/FamilySocialFeaturedCollection"}}}}}}}}},"400":{"description":"Invalid placement or limit."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Public. A signed-in parent (cookie or account credential) is optional: with one, `following` and saved times are filled, blocked owners answer 404, and the response is `private, no-store`; without one it is `public, max-age=60`. Machine keys and delegated service requests read as signed out.","x-curl-examples":[{"title":"List featured shelves","code":"curl \"https://apis.schoolsoutapp.com/api/featured-collections\""}]}},"/api/tracking":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"List follows","description":"The account's followed organizations, activities and sessions with their alerts, newest first, plus waitlist statistics. Every target is resolved in one query.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":500},"description":"Page size."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"nextCursor from the previous page."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["count","limit","tracked","nextCursor","waitlist"],"properties":{"count":{"type":"integer","description":"All of the account's follows, across pages."},"limit":{"type":["integer","null"],"description":"The most follows an account may have; null while follows are uncapped."},"nextCursor":{"type":["string","null"],"description":"Opaque; pass as cursor for the next page. Null on the last page."},"tracked":{"type":"array","items":{"$ref":"#/components/schemas/FamilySocialTrackedRecord"}},"waitlist":{"type":"object","required":["catalog","tracked"],"properties":{"catalog":{"type":"object","required":["sessionsWithWaitlist","organizationsWithWaitlistMention","organizationsChecked"],"properties":{"sessionsWithWaitlist":{"type":"integer"},"organizationsWithWaitlistMention":{"type":"integer"},"organizationsChecked":{"type":"integer"}}},"tracked":{"type":"object","required":["sessionsWithWaitlist","organizationsWithWaitlistMention","organizationsChecked"],"properties":{"sessionsWithWaitlist":{"type":"integer"},"organizationsWithWaitlistMention":{"type":"integer"},"organizationsChecked":{"type":"integer"}}}}}}}}}}}},"400":{"description":"No account viewer, or an invalid limit or cursor."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account (cookie, account credential, or the service credential with x-viewer-id for Intelligence). Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"List follows","code":"curl \"https://apis.schoolsoutapp.com/api/tracking\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal family social"],"x-internal":true,"summary":"Follow a record","description":"Follows a catalog record. Following it again with `alerts` replaces them; without `alerts` it keeps them, and a new follow takes the kind's defaults.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["targetKind","targetId"],"properties":{"targetKind":{"enum":["organization","activity","session"]},"targetId":{"type":"string","format":"uuid"},"alerts":{"type":"array","items":{"enum":["registration_opens","spot_opens","dates_posted","updates"]},"description":"What to hear about. Omitted on a new follow: organization → updates, registration_opens; activity → registration_opens, dates_posted; session → spot_opens. Omitted on a repeat follow: the follow keeps its alerts. [] follows without alerts."}}}}}},"responses":{"200":{"description":"Already followed; alerts replaced.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["tracked","created"],"properties":{"tracked":{"$ref":"#/components/schemas/FamilySocialTrackedRecord"},"created":{"type":"boolean"}}}}}}}},"201":{"description":"Followed.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["tracked","created"],"properties":{"tracked":{"$ref":"#/components/schemas/FamilySocialTrackedRecord"},"created":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid target or alerts."},"401":{"description":"Current account required."},"404":{"description":"Not in the catalog."},"409":{"description":"The follow cap is reached (only while it is on)."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account (cookie, account credential, or the service credential with x-viewer-id for Intelligence). Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Follow a record","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/tracking\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"targetKind\":\"activity\",\"targetId\":\"$ACTIVITY_ID\"}'"}]},"delete":{"tags":["Internal family social"],"x-internal":true,"summary":"Unfollow a record","description":"Stops following a record.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["targetKind","targetId"],"properties":{"targetKind":{"enum":["organization","activity","session"]},"targetId":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["removed","targetKind","targetId"],"properties":{"removed":{"type":"boolean"},"targetKind":{"type":"string"},"targetId":{"type":"string","format":"uuid"}}}}}}}},"400":{"description":"Invalid target."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account (cookie, account credential, or the service credential with x-viewer-id for Intelligence). Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Unfollow a record","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/tracking\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"targetKind\":\"activity\",\"targetId\":\"$ACTIVITY_ID\"}'"}]}},"/api/users/viewer/profile":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"Read your public profile","description":"How other parents see you and your privacy choices. Version 0 means nothing has been saved yet.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["profile"],"properties":{"profile":{"$ref":"#/components/schemas/FamilySocialProfile"}}}}}}}},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Read your public profile","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/profile\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"patch":{"tags":["Internal family social"],"x-internal":true,"summary":"Change privacy","description":"Versioned write. Turning sharePlans on or off applies to every friendship at once.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["version","changes"],"properties":{"version":{"type":"integer","minimum":0},"changes":{"type":"object","required":["privacy"],"properties":{"privacy":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"sharePlans":{"type":"boolean"},"childProfilesVisible":{"type":"boolean"},"dataSharing":{"type":"boolean"}}}},"additionalProperties":false}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["profile"],"properties":{"profile":{"$ref":"#/components/schemas/FamilySocialProfile"}}}}}}}},"400":{"description":"Invalid version or privacy value."},"401":{"description":"Current account required."},"409":{"description":"Stale version."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Change privacy","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/profile\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":1,\"changes\":{\"privacy\":{\"sharePlans\":true}}}'"}]}},"/api/users/viewer/profile/photo":{"put":{"tags":["Internal family social"],"x-internal":true,"summary":"Upload your photo","description":"Body is the image (image/jpeg or image/png, at most 2 MiB and 4096 px a side). It is cropped to a square and stored as a 512×512 JPEG without metadata.","requestBody":{"required":true,"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["profile"],"properties":{"profile":{"$ref":"#/components/schemas/FamilySocialProfile"}}}}}}}},"401":{"description":"Current account required."},"413":{"description":"Larger than 2 MiB."},"415":{"description":"Not image/jpeg or image/png."},"422":{"description":"Cannot be decoded, or too large in pixels."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Upload your photo","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/profile/photo\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"delete":{"tags":["Internal family social"],"x-internal":true,"summary":"Remove your photo","description":"Removes the photo.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["profile"],"properties":{"profile":{"$ref":"#/components/schemas/FamilySocialProfile"}}}}}}}},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Remove your photo","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/profile/photo\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/people/{userId}/photo":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"Read a parent's photo","description":"The JPEG. With `v` naming the current photo it is cached immutably for a year.","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"v","in":"query","required":false,"schema":{"type":"string"},"description":"The photo's hash, from photoUrl."}],"responses":{"200":{"description":"The photo.","content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"No photo."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Public; anyone with the link.","x-curl-examples":[{"title":"Read a parent's photo","code":"curl \"https://apis.schoolsoutapp.com/api/people/$USER_ID/photo\""}]}},"/api/users/viewer/friends":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"List friends","description":"One relation, newest first, keyset-paged. `requested` includes requests the other parent declined, so asking again stays quiet.","parameters":[{"name":"relation","in":"query","required":false,"schema":{"enum":["friends","incoming","requested","blocked"],"default":"friends"},"description":"Which list."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"nextCursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["friends","nextCursor"],"properties":{"friends":{"type":"array","items":{"$ref":"#/components/schemas/FamilySocialFriend"}},"nextCursor":{"type":["string","null"],"description":"Opaque; pass as cursor for the next page. Null on the last page."}}}}}}}},"400":{"description":"Invalid relation, cursor or limit."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"List friends","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/friends\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/friends/{userId}":{"put":{"tags":["Internal family social"],"x-internal":true,"summary":"Change a friendship","description":"request (accepts at once when they asked), accept, decline, cancel, remove, block (from any relation) or unblock (by the blocker). Repeating an action that already holds answers the current relation. A request notifies the other parent; an acceptance notifies the requester.","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"enum":["request","accept","decline","cancel","remove","block","unblock"]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["relation"],"properties":{"relation":{"$ref":"#/components/schemas/FamilySocialRelation"}}}}}}}},"400":{"description":"Invalid action."},"401":{"description":"Current account required."},"404":{"description":"Unknown parent, one without a family profile, yourself, or one who blocked you."},"409":{"description":"Not allowed from the current relation, or the friend cap is reached (only while it is on)."},"429":{"description":"Daily request limit; retryAfterMs is how many milliseconds until another request is allowed. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Change a friendship","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/friends/$USER_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"action\":\"request\"}'"}]}},"/api/users/viewer/friends-going":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"Friends going","description":"For one page of cards or a business page: friends whose relation is friends and who share plans, signed up for these activities, sessions or organizations, one group per asked record a friend signed up for. A sign-up is a booking that is booked or waitlisted, or a registered external plan, whose session is upcoming, ongoing or ended at most 365 days before today in the viewer's household. Children's names only when that friend shows them.","parameters":[{"name":"activityIds","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated activity IDs."},{"name":"sessionIds","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated session IDs."},{"name":"organizationIds","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated organization IDs: who booked at each business, across its activities and sessions."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["going"],"properties":{"going":{"type":"array","items":{"$ref":"#/components/schemas/FamilySocialFriendsGoing"}}}}}}}}},"400":{"description":"Give exactly one of activityIds, sessionIds or organizationIds, at most 100 UUIDs."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Friends going","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/friends-going\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/reports":{"post":{"tags":["Internal family social"],"x-internal":true,"summary":"Report a person, listing or collection","description":"One report per reporter and target. The first answers 201 with `alreadyReported: false`; repeating it is idempotent: the first report stands (its reason and note too) and the answer is 200 with `alreadyReported: true`. Staff review reports at /api/family-reports.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["targetKind","targetId","reason"],"properties":{"targetKind":{"enum":["person","activity","organization","collection"]},"targetId":{"type":"string","format":"uuid"},"reason":{"enum":["fake","inappropriate","spam","other"]},"note":{"type":"string","maxLength":500}}}}}},"responses":{"200":{"description":"Already reported; the first report stands.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["reported","alreadyReported"],"properties":{"reported":{"const":true},"alreadyReported":{"type":"boolean","description":"False on the 201 that recorded it, true on a repeat."}},"additionalProperties":false}}}}}},"201":{"description":"Recorded.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["reported","alreadyReported"],"properties":{"reported":{"const":true},"alreadyReported":{"type":"boolean","description":"False on the 201 that recorded it, true on a repeat."}},"additionalProperties":false}}}}}},"400":{"description":"Invalid target, reason or note."},"401":{"description":"Current account required."},"404":{"description":"The target is not visible to you."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Report a person, listing or collection","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/reports\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"targetKind\":\"person\",\"targetId\":\"$USER_ID\",\"reason\":\"spam\"}'"}]}},"/api/people/{userId}":{"get":{"tags":["Internal family social"],"x-internal":true,"summary":"Read another parent","description":"Their public profile, relation, their 50 most recently changed public collections and, when you are friends and they share plans, what they are going to.","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["person"],"properties":{"person":{"$ref":"#/components/schemas/FamilySocialPerson"}}}}}}}},"401":{"description":"Current account required."},"404":{"description":"Unknown, without a family profile, yourself, or blocked either way."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Read another parent","code":"curl \"https://apis.schoolsoutapp.com/api/people/$USER_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/family-reports":{"get":{"tags":["Internal family moderation"],"x-internal":true,"summary":"Review family reports","description":"Staff queue of parents' reports in one status, newest first.","parameters":[{"name":"status","in":"query","required":false,"schema":{"enum":["open","reviewing","actioned","dismissed"],"default":"open"},"description":"Which reports."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"nextCursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":100},"description":"Page size."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["reports","nextCursor"],"properties":{"reports":{"type":"array","items":{"$ref":"#/components/schemas/FamilyModerationReport"}},"nextCursor":{"type":["string","null"],"description":"Opaque; pass as cursor for the next page. Null on the last page."}}}}}}}},"400":{"description":"Invalid status, cursor or limit."},"401":{"description":"Current account required."},"403":{"description":"Direct staff/ADMIN account required."}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Direct current staff or ADMIN account session or credential. Service delegation, machine keys and preview viewers are rejected.","x-curl-examples":[{"title":"Review family reports","code":"curl \"https://apis.schoolsoutapp.com/api/family-reports\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/family-reports/{reportId}":{"patch":{"tags":["Internal family moderation"],"x-internal":true,"summary":"Move a family report","description":"Moves a report through review: open → reviewing → actioned or dismissed; open may be resolved directly and reviewing released back to open. actioned and dismissed are final. `note` replaces the staff note. The staff account is recorded, and resolving sets resolvedAt.","parameters":[{"name":"reportId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"enum":["open","reviewing","actioned","dismissed"]},"note":{"type":"string","minLength":1,"maxLength":1000}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["report"],"properties":{"report":{"$ref":"#/components/schemas/FamilyModerationReport"}}}}}}}},"400":{"description":"Invalid status or note."},"401":{"description":"Current account required."},"403":{"description":"Direct staff/ADMIN account required."},"404":{"description":"Report not found."},"409":{"description":"Not allowed from the report's current status."}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Direct current staff or ADMIN account session or credential. Service delegation, machine keys and preview viewers are rejected.","x-curl-examples":[{"title":"Move a family report","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/family-reports/{reportId}\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"status\":\"reviewing\"}'"}]}},"/api/health":{"get":{"description":"Returns connected: true when the API can reach its database. It does not expose database or credential details.","responses":{"200":{"content":{"application/json":{"example":{"data":{"connected":true},"success":true}}},"description":"Connection status"}},"security":[],"summary":"Check availability","tags":["System"],"x-auth":"No key required","x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/health\"","title":"Availability"}]}},"/api/ingestion/crawler-jobs":{"get":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Staff-only list with search, status, page and pageSize filters. Returns data.jobs, total, page and pageSize. Job IDs are decimal strings; counts, depth and masks are integers. Duplicate submissions are independent jobs. Job counts always describe the latest crawl. Failed, blocked or cancelled jobs with an empty latest crawl may include saved_results {attempt_id, attempt_number, counts} for a prior crawl, ranked by retained rows, crawled rows and latest attempt number. Counts come from saved rows, independently of JEV sessions.","responses":{"200":{"description":"Paginated crawler jobs.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["jobs","page","pageSize","total"],"properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/CrawlerJob"}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","minimum":1},"pageSize":{"type":"integer","minimum":1}}}}}}}},"400":{"description":"Invalid filters."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account required."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"List crawler jobs","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl --get \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  --data-urlencode \"status=completed\" \\\n  --data-urlencode \"page=1\" \\\n  --data-urlencode \"pageSize=25\" -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"List completed crawler jobs"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]},"post":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Search mode requires search_term and uses contract 6 when search-domain arrays are empty, or contract 7 when either search_include_domains or search_exclude_domains is nonempty. Both canonical arrays are returned on every job. Organization mode and activity filtering retain contract 5: fetched site evidence, explicit identity/activity assessments and acquisition usage. Acquisition caps are independently nullable; query and candidate caps default to null. Historical contract 3 jobs retain positive integer budgets. Aggregators never qualify. Uncertain results remain unresolved. Gated exports contain only eligible site roots for Trident; URL-only JEV and confidence overrides are rejected. Ordinary search queues provider search followed by optional URL/summary JEV, crawling, and fetched-page evidence JEV. Explicit search domains restrict provider queries and candidate admission before JEV/seed fetch, with exact-host/subdomain matching and exclusions winning. They do not sandbox later crawl discovery or redirects. Canonical page evidence is frozen even without initial JEV; historical contracts 1 through 5 retain URL-only page evaluation. Every request creates a new job. Cookie mutations require CSRF. Credentials belong to workers. At least one active provider bit (1 through 32) is required. Retired provider bits 64 and 128 are rejected for new jobs; historical jobs remain readable.","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"acquisition_budget":{"additionalProperties":false,"description":"Optional overrides for gated jobs only. Each member is a positive integer cap or null for no caller cap. Every omitted member defaults to null. Explicit limits are positive signed 32-bit integers. When both are capped, max_pages_per_site must not exceed max_pages.","properties":{"max_candidate_sites":{"default":null,"maximum":2147483647,"minimum":1,"type":["integer","null"]},"max_model_requests":{"default":null,"maximum":2147483647,"minimum":1,"type":["integer","null"]},"max_pages":{"default":null,"maximum":2147483647,"minimum":1,"type":["integer","null"]},"max_pages_per_site":{"default":null,"maximum":2147483647,"minimum":1,"type":["integer","null"]},"max_search_queries":{"default":null,"maximum":2147483647,"minimum":1,"type":["integer","null"]}},"type":"object"},"activity_filter":{"default":false,"description":"Select only first-party sites with fetched activity evidence. New gated jobs use contract 5 and prohibit JEV settings bit 4 and jev_config.","type":"boolean"},"allow_unlimited":{"default":false,"type":"boolean"},"jev_config":{"additionalProperties":false,"properties":{"model":{"default":"jev-1.13.0","type":"string"},"prompt":{"maxLength":16000,"minLength":1,"type":"string"},"settings":{"description":"Only confidence_threshold, batch_size, concurrency, timeout_seconds, max_payload_bytes, max_request_tokens, max_context_tokens and token_buffer_ratio are allowed. batch_size=0 uses adaptive token batching; a positive value adds an item-count ceiling. Token limits apply to state plus all questions and state plus the longest question respectively; token_buffer_ratio reserves headroom in both budgets (default 0.2). confidence_threshold is the minimum yes probability in [0, 1] (default 0.5 for new configurations). No credentials.","type":"object"}},"required":["prompt"],"type":"object"},"max_crawl_depth":{"default":null,"description":"Seed depth is zero. Null or omission means unlimited depth.","maximum":2147483647,"minimum":0,"type":["integer","null"]},"max_urls_per_site":{"default":null,"description":"Unique accepted destinations discovered per source registrable domain across all pages and sitemaps. Null or omission means no caller cap.","maximum":2147483647,"minimum":1,"type":["integer","null"]},"mode":{"default":"search","description":"Organization mode requires organization.name and derives search_term when omitted. It enables activity_filter.","enum":["search","organization_website"],"type":"string"},"organization":{"additionalProperties":false,"properties":{"address":{"maxLength":2000,"type":"string"},"description":{"maxLength":4000,"type":"string"},"email":{"maxLength":2000,"type":"string"},"location":{"maxLength":2000,"type":"string"},"name":{"maxLength":300,"type":"string"},"phone":{"maxLength":2000,"type":"string"},"registration_url":{"maxLength":2000,"type":"string"},"source_url":{"maxLength":2000,"type":"string"},"website_url":{"maxLength":2000,"type":"string"}},"required":["name"],"type":"object"},"provider_mask":{"description":"Google 1, Bing 2, DuckDuckGo 4, Brave 8, Startpage 16, Qwant 32.","maximum":63,"minimum":1,"type":"integer"},"search_exclude_domains":{"description":"Optional bare public DNS names with the same canonical normalization. Exact-host/subdomain exclusions win overlapping includes. Omitted/null/empty means no exclusion; nonempty policy selects contract 7.","items":{"type":"string"},"type":["array","null"]},"search_include_domains":{"description":"Optional bare public DNS names. API normalizes Unicode using nontransitional UTS46 to canonical lowercase ASCII and deduplicates. Omitted/null/empty means unrestricted. Nonempty policy selects contract 7 and is rejected for gated jobs. Restricts provider search and result admission, including subdomains; later crawl/redirect policies remain independent.","items":{"type":"string"},"type":["array","null"]},"search_term":{"maxLength":2000,"minLength":1,"type":"string"},"settings_mask":{"description":"CAPTCHA solving 1, proxies 2, JEV filtering 4, allow external URLs 8. External links are disabled by default; subdomains of the same registrable domain remain eligible.","maximum":15,"minimum":0,"type":"integer"}},"required":["provider_mask","settings_mask"],"type":"object"}}},"required":true},"responses":{"201":{"description":"Independent queued job in data.job.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["job"],"properties":{"job":{"$ref":"#/components/schemas/CrawlerJob"}}}}}}}},"400":{"description":"Invalid fields, flags, depth, or JEV configuration."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account required."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Create a crawler job","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl -X POST \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"search_term\":\"summer day camps Seattle\",\"provider_mask\":3,\"settings_mask\":0,\"max_crawl_depth\":2}' -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Queue a Google and Bing search crawl two links deep"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-jobs/stream":{"get":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Same filters as the job list. Request-based SSE: event:snapshot data is the raw list data projection without a JSON success envelope. Fresh snapshot on every connection, changed snapshots thereafter, and heartbeat comments. Queued job.queue includes state (scheduled, no_active_workers, unavailable_capabilities, workers_busy, ready), message, available_at, active_workers, compatible_workers, ready_workers, bounded workers diagnostics and workers_truncated. Fresh per-worker capabilities are matched together; queue changes stream independently of job.updated_at. Connection lifetime is bounded to renew authorization; reconnect does not restart work. Terminal event:error contains a sanitized message.","responses":{"200":{"content":{"text/event-stream":{"schema":{"type":"string"}}},"description":"SSE snapshots."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account required."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Stream crawler job list snapshots","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl -N --get \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/stream\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  --data-urlencode \"status=running\" -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Stream running crawler job snapshots"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-jobs/{id}":{"get":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Returns data.job, attempts (including nullable evaluation_id to distinguish re-JEV attempts), bounded meaningful events and results with explicit totals/truncation, optional latest_progress for the current attempt, and evaluations. Exact progress and crawl_progress kinds are excluded from events and its count; unknown kinds are preserved. A queued re-JEV session has no latest_progress. job.latest_event remains the newest raw event. Preview results omit discovery_path; use the paged results endpoint for saved ancestry. Worker artifacts, credentials and page bodies remain private. Job counts always describe the latest crawl. Failed, blocked or cancelled jobs with an empty latest crawl may include saved_results {attempt_id, attempt_number, counts} for a prior crawl, ranked by retained rows, crawled rows and latest attempt number. Counts come from saved rows, independently of JEV sessions. Gated job detail also includes result.site_assessment and job.site_resolution with explicit identity/activity outcomes, evidence, provenance, and acquisition usage.","parameters":[{"in":"path","name":"id","required":true,"schema":{"pattern":"^[1-9][0-9]*$","type":"string"}}],"responses":{"200":{"description":"Crawler detail.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/CrawlerDetail"}}}}}},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account required."},"404":{"description":"Job not found."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Read detailed crawler history","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl --get \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/$CRAWLER_JOB_ID\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Read one crawler job"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-jobs/{id}/cancel":{"post":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Stops queued or running crawls with persisted status cancelled and reason Stopped by user. Revokes the active crawl claim and finalizes its original JEV session with already saved URLs. Saved results, judgments and history remain available. Separate re-JEV sessions are unaffected. Already terminal jobs are returned unchanged, including when completion wins a concurrent race. Cookie mutations require CSRF.","parameters":[{"in":"path","name":"id","required":true,"schema":{"pattern":"^[1-9][0-9]*$","type":"string"}}],"responses":{"200":{"description":"Current job in data.job.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["job"],"properties":{"job":{"$ref":"#/components/schemas/CrawlerJob"}}}}}}}},"400":{"description":"Invalid job ID."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account and cookie CSRF required."},"404":{"description":"Job not found."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Stop a crawler job","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl -X POST \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/$CRAWLER_JOB_ID/cancel\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Stop a queued or running crawl"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-jobs/{id}/evaluations":{"get":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Returns data.evaluations, total, page and pageSize. Detail snapshots include the most recent 25 sessions with evaluation_count/evaluations_truncated; this endpoint retrieves earlier preserved sessions. Generic URL-relevance sessions remain unavailable on organization/activity-gated jobs. Trident sessions are available on either job mode; they have purpose trident_summer_camp and kind site_vetting. Only eligible assessments from completed or partial vetting evaluations enter output_urls; unresolved, missing, rejected and unfinished decisions never enter exports. Trident rejects baseline and confidence overrides and preserves source_attempt_id independently of its own attempt_id.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"page","schema":{"minimum":1,"type":"integer"}},{"in":"query","name":"pageSize","schema":{"maximum":100,"minimum":1,"type":"integer"}}],"responses":{"200":{"description":"Saved JEV evaluation history.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["evaluations","page","pageSize","total"],"properties":{"evaluations":{"type":"array","items":{"$ref":"#/components/schemas/CrawlerEvaluation"}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","minimum":1},"pageSize":{"type":"integer","minimum":1}}}}}}}},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account required."},"404":{"description":"Job not found."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Page JEV session history","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl --get \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/$CRAWLER_JOB_ID/evaluations\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  --data-urlencode \"page=1\" \\\n  --data-urlencode \"pageSize=25\" -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Page earlier JEV sessions"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]},"post":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Terminal source job required. Freezes saved crawled final URLs and baseline membership; contract 6 also freezes the complete saved typed page evidence. Missing contract 6 evidence rejects enqueue with 409, and combined URLs/evidence over the request budget reject with 400. No searching, refetching or URL-only fallback occurs. Historical contracts 1 through 5 retain URL-only evaluation. Required jev_config.prompt creates an immutable version. Omitted model, confidence_threshold, concurrency and timeout_seconds inherit the original. New sessions use current defaults for batch_size, max_payload_bytes, max_request_tokens, max_context_tokens and token_buffer_ratio unless explicitly supplied. Optional baseline_evaluation_id must refer to a finalized evaluation from this job. Original results and history remain unchanged. Cookie mutations require CSRF. Generic URL-relevance sessions remain unavailable on organization/activity-gated jobs. Trident sessions are available on either job mode; they have purpose trident_summer_camp and kind site_vetting. Only eligible assessments from completed or partial vetting evaluations enter output_urls; unresolved, missing, rejected and unfinished decisions never enter exports. Trident rejects baseline and confidence overrides and preserves source_attempt_id independently of its own attempt_id.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"baseline_evaluation_id":{"type":"string"},"jev_config":{"properties":{"model":{"type":"string"},"prompt":{"type":"string"},"settings":{"type":"object"}},"required":["prompt"],"type":"object"}},"required":["jev_config"],"type":"object"}}},"required":true},"responses":{"202":{"description":"New queued data.evaluation.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["evaluation"],"properties":{"evaluation":{"$ref":"#/components/schemas/CrawlerEvaluation"}}}}}}}},"400":{"description":"Invalid JEV configuration."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account required."},"404":{"description":"Job or baseline not found."},"409":{"description":"Source or baseline is not finalized."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Queue a new JEV session","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl -X POST \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/$CRAWLER_JOB_ID/evaluations\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"jev_config\":{\"prompt\":\"Does this page describe a youth summer camp program?\"}}' -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Re-judge saved URLs with a new prompt"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-jobs/{id}/evaluations/{evaluationId}":{"get":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Returns data.evaluation and data.comparison with added, removed, unchanged, errors, legacy uncertain, pending and output_urls arrays. Counts include decided (yes or no), errors, pending and legacy uncertain outcomes over the canonical candidate total, plus added and removed output URLs against the session's saved baseline (also in history and job detail evaluations). Optional baseline selects a finalized evaluation from the same job. Request errors and pending judgments preserve baseline membership; errors do not add newly included outputs. Generic URL-relevance sessions remain unavailable on organization/activity-gated jobs. Trident sessions are available on either job mode; they have purpose trident_summer_camp and kind site_vetting. Only eligible assessments from completed or partial vetting evaluations enter output_urls; unresolved, missing, rejected and unfinished decisions never enter exports. Trident rejects baseline and confidence overrides and preserves source_attempt_id independently of its own attempt_id.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"evaluationId","required":true,"schema":{"type":"string"}},{"in":"query","name":"baseline","schema":{"type":"string"}}],"responses":{"200":{"description":"Evaluation and comparison.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/CrawlerEvaluationDetail"}}}}}},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account required."},"404":{"description":"Evaluation not found in this job."},"409":{"description":"Selected baseline is queued or running."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Compare saved JEV outputs","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl --get \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/$CRAWLER_JOB_ID/evaluations/$EVALUATION_ID\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Compare a JEV session with its saved baseline"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-jobs/{id}/evaluations/{evaluationId}/cancel":{"post":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Cancels a queued or running rejudge or site_vetting evaluation and its active execution attempt. Original crawl evaluations must be stopped through the source-job cancel route; this endpoint rejects them with 400. Source crawl status, fetched pages and saved partial judgments/assessments remain unchanged. Cancelling a terminal evaluation is idempotent. Worker lease/control fences reject subsequent publication, renewal and completion. Cookie mutations require CSRF.","parameters":[{"in":"path","name":"id","required":true,"schema":{"pattern":"^[1-9][0-9]*$","type":"string"}},{"in":"path","name":"evaluationId","required":true,"schema":{"pattern":"^[1-9][0-9]*$","type":"string"}}],"responses":{"200":{"description":"Current evaluation in data.evaluation.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["evaluation"],"properties":{"evaluation":{"$ref":"#/components/schemas/CrawlerEvaluation"}}}}}}}},"400":{"description":"Invalid ID, unsupported request body, or original crawl evaluation."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account and cookie CSRF required."},"404":{"description":"Job or evaluation not found in this job."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Stop a saved-URL evaluation","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl -X POST \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/$CRAWLER_JOB_ID/evaluations/$EVALUATION_ID/cancel\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Stop a saved-URL evaluation"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-jobs/{id}/export":{"get":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Downloads distinct sorted included final URLs from saved results only, excluding explicit exclusions and JEV errors. Optional attempt selects an owned crawl attempt; optional evaluation selects a JEV session. These selectors are mutually exclusive. Omission uses the latest crawl attempt. Explicit selection is included in the attachment filename. Optional confidence_threshold selects the same membership as the matching results read. Gated jobs export only evidence-qualified site roots and reject evaluation selection or confidence_threshold with 400.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"evaluation","schema":{"type":"string"}},{"in":"query","name":"attempt","schema":{"pattern":"^[1-9][0-9]*$","type":"string"}},{"description":"Read-time minimum yes confidence. Attempt scope requires a JEV-filtered job; evaluation scope accepts any session.","in":"query","name":"confidence_threshold","schema":{"maximum":1,"minimum":0,"type":"number"}}],"responses":{"200":{"content":{"application/x-ndjson":{"schema":{"type":"string"}}},"description":"URL JSONL attachment."},"400":{"description":"Invalid or conflicting selection, or an invalid confidence_threshold."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account required."},"404":{"description":"Job, crawl attempt or evaluation not found."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Export selected crawler URLs","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl --get \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/$CRAWLER_JOB_ID/export\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  --data-urlencode \"evaluation=$EVALUATION_ID\" \\\n  --output \"crawler-urls.jsonl\" -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Download URLs selected by a JEV session"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-jobs/{id}/results":{"get":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Optional attempt selects a saved non-evaluation crawl attempt belonging to this job; omission uses the latest crawl attempt, including empty retries. The response identifies attempt (null before any attempt starts), unfiltered counts for that attempt, and filtered total from one read snapshot. Each result may contain discovery_path: status (complete or partial), root providers, ordered steps (url and via: search, link, redirect, discovery, unknown), and reason. Complete paths begin at a known search provider; partial paths preserve an honest suffix and explain missing ancestry. Absent paths mean the attempt did not save ancestry. Paths have no step-count ceiling and retain a 64 KiB record bound and never require loading a whole-crawl graph or worker filesystem. Decisive yes/no JEV verdicts include nullable yes_probability and no_probability (0-1, both present or both null); a yes below the configuration's confidence_threshold is saved as no with reason yes_confidence_below_threshold. Optional search is a case-insensitive URL substring. Trident evaluation rows also expose site_assessment with grounded evidence, decision and actual homepage/camp entry; membership accepts eligible assessments only after vetting is completed or partial and rejects baseline/confidence overrides. Optional evaluation selects one JEV session of this job instead of a crawl attempt and cannot be combined with attempt or excluded. Its rows are the session's canonical candidate final URLs plus comparison-baseline or output URLs outside them, ordered by depth then URL. Each row adds included (in the session output, the same set as comparison.output_urls and the session export) and baseline_included; excluded is !included. verdict, reason and probabilities come from this session's judgments (null verdict: no judgment for this URL); url, depth, providers, status, fetched_at and discovery_path come from the saved crawl row for that final URL. Optional baseline selects a finished comparison session (default: the session's saved baseline; queued or running is 409). membership (default all) selects included, excluded, or changed (included differs from baseline_included). Evaluation responses contain data.evaluation, baseline_evaluation_id (null means original crawl outputs), unfiltered counts {total, included, excluded, changed, added, removed, errors, pending, uncertain}, results, filtered total, page and pageSize. Optional confidence_threshold re-applies a minimum yes probability to saved decisions without rerunning JEV: a saved model yes (relevant, or no with reason yes_confidence_below_threshold) counts as included only when yes_probability is at least the threshold; model no, decisions without probabilities, uncertain, error and unjudged rows keep their saved membership. It changes membership filters, total, counts (attempt: retained and excluded; evaluation: included, excluded, changed, added, removed) and each row's excluded/included, never the saved verdict, reason or probabilities. In evaluation mode it applies only to the selected session's judgments, never to its baseline URLs or a baseline session, and is accepted for any session, including re-JEV of a crawl without JEV filtering; attempt mode requires a JEV-filtered job. Gated jobs reject evaluation selection and confidence_threshold with 400; gated results are one row per candidate site root with site_assessment evidence and explicit eligibility. site_resolution describes the selected attempt. Optional preview=1 requires a Trident evaluation and membership=all. Before completed/partial final settlement, it adds per-row site_preview (the existing SiteAssessment shape with grounded citations), preview_attempt_id, preview_revision (unique checked-root count as a decimal string), and unfiltered preview_counts {total, checked, eligible, ineligible, uncertain, errors, pending}. The five outcome categories are disjoint and sum to total; unresolved blocked checks are errors, other unresolved checks uncertain, and roots without previews pending. Previews are unpublished and never grant output/export membership. Failed/cancelled previews remain inspectable; queued/new attempts isolate prior previews. Successful settlement omits preview fields and supplies authoritative site_assessment. Legacy workers without typed previews or a qualification progress preview_revision omit preview metadata. The qualification progress revision is scoped to its attempt and triggers a page refresh; the paged response is one consistent snapshot.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"page","schema":{"minimum":1,"type":"integer"}},{"in":"query","name":"pageSize","schema":{"maximum":100,"minimum":1,"type":"integer"}},{"in":"query","name":"search","schema":{"maxLength":2000,"type":"string"}},{"in":"query","name":"excluded","schema":{"type":"boolean"}},{"in":"query","name":"attempt","schema":{"pattern":"^[1-9][0-9]*$","type":"string"}},{"in":"query","name":"evaluation","schema":{"pattern":"^[1-9][0-9]*$","type":"string"}},{"description":"Requires evaluation.","in":"query","name":"baseline","schema":{"pattern":"^[1-9][0-9]*$","type":"string"}},{"description":"Requires evaluation.","in":"query","name":"membership","schema":{"default":"all","enum":["all","included","excluded","changed"],"type":"string"}},{"description":"Read-time minimum yes confidence; same meaning as the JEV setting. Attempt mode requires a JEV-filtered job; evaluation mode accepts any session. Omission keeps saved decisions.","in":"query","name":"confidence_threshold","schema":{"maximum":1,"minimum":0,"type":"number"}},{"description":"Opt-in unpublished per-site Trident inspection; requires evaluation and membership=all.","in":"query","name":"preview","schema":{"enum":["1"],"type":"string"}}],"responses":{"200":{"description":"Attempt mode: data.results, total, page, pageSize, attempt and unfiltered counts. Evaluation mode: data.evaluation, baseline_evaluation_id, counts, results, total, page and pageSize.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"oneOf":[{"$ref":"#/components/schemas/CrawlerResultsPage"},{"$ref":"#/components/schemas/CrawlerEvaluationResultsPage"}]}}}}}},"400":{"description":"Invalid or conflicting selection or filter, including a confidence_threshold that is repeated, not a decimal in [0, 1], or used for attempt results of a job without JEV filtering."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account required."},"404":{"description":"Job, attempt, evaluation or baseline not found."},"409":{"description":"Selected comparison baseline is queued or running."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Page saved crawler URLs","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl --get \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/$CRAWLER_JOB_ID/results\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  --data-urlencode \"excluded=false\" \\\n  --data-urlencode \"page=1\" \\\n  --data-urlencode \"pageSize=50\" -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Page included URLs from the latest crawl"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-jobs/{id}/stream":{"get":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. event:snapshot carries raw detail data. Every reconnect starts with current state; stream interruption never cancels work. Queued job.queue uses the same live eligibility projection as the list, including heartbeat staleness transitions without job writes. event:error contains only a sanitized message.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"text/event-stream":{"schema":{"type":"string"}}},"description":"SSE snapshots."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account required."},"404":{"description":"Job not found."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Stream crawler detail snapshots","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl -N --get \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/$CRAWLER_JOB_ID/stream\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Stream one crawler job"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-jobs/{id}/trident-evaluations":{"post":{"description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Explicitly queues independent Trident summer-camp vetting. Requires a terminal source crawl/job, including failed or cancelled sources with complete immutable fetched-page evidence. Optional attempt_id selects a source crawl attempt; omission selects the latest terminal crawl. Saves distinct site roots, all frozen pages, purpose, source provenance and a job-scoped idempotency key. Matching retries return the same evaluation; conflicting source reuse returns 409. Performs no new search or fetch. Missing historical evidence requires a recrawl. Cookie mutations require CSRF. Completion and export belong to this evaluation independently of the source crawl status.","parameters":[{"in":"path","name":"id","required":true,"schema":{"pattern":"^[1-9][0-9]*$","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"attempt_id":{"pattern":"^[1-9][0-9]*$","type":"string"},"idempotency_key":{"description":"Nonblank key of at most 200 UTF-8 bytes; surrounding whitespace is invalid.","maxLength":200,"minLength":1,"type":"string"}},"required":["idempotency_key"],"type":"object"}}},"required":true},"responses":{"202":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"evaluation":{"$ref":"#/components/schemas/CrawlerEvaluation"}},"type":"object"}},"type":"object"}}},"description":"Queued or matching saved evaluation in data.evaluation."},"400":{"description":"Invalid request or unsupported configuration."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN account and cookie CSRF required."},"404":{"description":"Job not found."},"409":{"description":"Source is active, idempotency conflicts, or saved evidence requires a recrawl."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"security":[{"CrawlerCapability":[]}],"summary":"Vet saved websites for their own summer camps","tags":["Internal crawler monitoring"],"x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","x-curl-examples":[{"code":"curl -X POST \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-jobs/$CRAWLER_JOB_ID/trident-evaluations\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"idempotency_key\":\"trident-vetting-2026-10-10\"}' -H \"Origin: $INTELLIGENCE_ORIGIN\"","title":"Vet the latest terminal crawl for summer camps"}],"x-internal":true,"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/geocoding/coverage":{"get":{"description":"Counts distinct configurations separately from affected entity references, ZIP coverage, precision, missing coordinates/boundaries, pending/unresolved work, dataset availability, credentials, and worker heartbeat. No provider call. A coverage calculation timeout returns 503 with a retryable message; failed calculations have a retry cooldown.","parameters":[{"description":"Optional US state name/code.","in":"query","name":"state","required":false,"schema":{"type":"string"}},{"description":"Optional recorded city label.","in":"query","name":"city","required":false,"schema":{"type":"string"}},{"description":"Optional US ZIP or ZIP+4.","in":"query","name":"postalCode","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/GeocodingCoverage"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Result"},"400":{"description":"Invalid scope, parameters, or missing boundary."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"503":{"description":"Coverage calculation timed out; retry after the calculation cooldown."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read automatic location coverage","tags":["Internal staff geocoding"],"x-auth":"Current staff or ADMIN operator identity. Intelligence uses its signed session cookie; cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/geocoding/coverage\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"state=WA\"","title":"Read Washington location coverage as a staff or ADMIN account"}],"x-internal":true}},"/api/ingestion/geocoding/jobs":{"get":{"description":"Paginated job history. Jobs retain their original scope, mode, quote, requester, outcomes, and timestamps.","parameters":[{"description":"Page size (1–100).","in":"query","name":"limit","required":false,"schema":{"maximum":100,"minimum":1,"type":"integer"}},{"description":"Zero-based page offset.","in":"query","name":"offset","required":false,"schema":{"minimum":0,"type":"integer"}},{"description":"Optional US state scope filter.","in":"query","name":"state","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"jobs":{"items":{"$ref":"#/components/schemas/GeocodingJob"},"type":"array"},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}},"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Result"},"400":{"description":"Invalid scope, parameters, or missing boundary."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"List historical backfill jobs","tags":["Internal staff geocoding"],"x-auth":"Current staff or ADMIN operator identity. Intelligence uses its signed session cookie; cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/geocoding/jobs\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"state=WA\" \\\n  --data-urlencode \"limit=25\" \\\n  --data-urlencode \"offset=0\"","title":"List the most recent Washington backfill jobs"}],"x-internal":true},"post":{"description":"Recomputes candidate eligibility and pricing transactionally. The fingerprint must match a fresh preview. An idempotency key returns the same job on a repeated confirmation. Freezes candidates and lookup budget; later records enter another job. rerunOf creates a new historical job. Cancellation and uncertain billing do not authorize replacement submissions.","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeocodingPreviewInput"},{"properties":{"fingerprint":{"type":"string"},"idempotencyKey":{"type":"string"}},"required":["fingerprint","idempotencyKey"],"type":"object"}]}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"job":{"$ref":"#/components/schemas/GeocodingJob"}},"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Confirmed job, including repeated idempotent confirmation."},"400":{"description":"Invalid scope, parameters, or missing boundary."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"409":{"description":"Quote changed or idempotency key conflicts. Obtain a fresh preview before confirming changed work."}},"security":[{"ApiKeyAuth":[]}],"summary":"Confirm a paid backfill","tags":["Internal staff geocoding"],"x-auth":"Current staff or ADMIN operator identity. Intelligence uses its signed session cookie; cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/geocoding/jobs\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data @geocoding-confirmation.json","title":"Confirm a reviewed paid preview using a JSON file containing its scope, mode, fingerprint, and idempotencyKey"}],"x-internal":true}},"/api/ingestion/geocoding/jobs/{id}":{"get":{"description":"Returns current progress and retained quote/outcomes. Blocked or uncertain provider work is visible.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"job":{"$ref":"#/components/schemas/GeocodingJob"}},"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Result"},"400":{"description":"Invalid scope, parameters, or missing boundary."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"404":{"description":"Job not found."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read one backfill job","tags":["Internal staff geocoding"],"x-auth":"Current staff or ADMIN operator identity. Intelligence uses its signed session cookie; cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/geocoding/jobs/$GEOCODING_JOB_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read the job identified by GEOCODING_JOB_ID"}],"x-internal":true}},"/api/ingestion/geocoding/jobs/{id}/cancel":{"post":{"description":"Stops unsent work. Requests already sent retain their outcomes and may settle after cancellation. Does not refund or resend a provider request. Rerun through a new preview and confirmation.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"job":{"$ref":"#/components/schemas/GeocodingJob"}},"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Result"},"400":{"description":"Invalid scope, parameters, or missing boundary."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"404":{"description":"Job not found."}},"security":[{"ApiKeyAuth":[]}],"summary":"Cancel new backfill submissions","tags":["Internal staff geocoding"],"x-auth":"Current staff or ADMIN operator identity. Intelligence uses its signed session cookie; cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/geocoding/jobs/$GEOCODING_JOB_ID/cancel\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Stop new submissions for the job identified by GEOCODING_JOB_ID"}],"x-internal":true}},"/api/ingestion/geocoding/jobs/{id}/items":{"get":{"description":"Returns staff-only configuration address snapshots, item statuses, accepted precision, and exclusion/failure reasons.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Page size (1–500).","in":"query","name":"limit","required":false,"schema":{"maximum":500,"minimum":1,"type":"integer"}},{"description":"Zero-based page offset.","in":"query","name":"offset","required":false,"schema":{"minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GeocodingItem"},"type":"array"},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}},"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Result"},"400":{"description":"Invalid scope, parameters, or missing boundary."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"404":{"description":"Job not found."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read backfill item outcomes","tags":["Internal staff geocoding"],"x-auth":"Current staff or ADMIN operator identity. Intelligence uses its signed session cookie; cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/geocoding/jobs/$GEOCODING_JOB_ID/items\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"limit=100\" \\\n  --data-urlencode \"offset=0\"","title":"Read the first page of configuration outcomes for a job"}],"x-internal":true}},"/api/ingestion/geocoding/preview":{"post":{"description":"Read-only quote with no Geocodio calls. Shows affected records/configurations, unique/reusable addresses, exclusions, required lookups, and estimated micro-USD cost before shared provider free credits. Excludes explicit non-US and conflicting state/ZIP evidence.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodingPreviewInput"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/GeocodingPreview"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Result"},"400":{"description":"Invalid scope, parameters, or missing boundary."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"Preview a paid backfill","tags":["Internal staff geocoding"],"x-auth":"Current staff or ADMIN operator identity. Intelligence uses its signed session cookie; cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/geocoding/preview\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"scope\":{\"state\":\"WA\"},\"mode\":\"gaps\"}'","title":"Preview Washington address gaps without calling the paid provider"}],"x-internal":true}},"/api/ingestion/geocoding/regions":{"get":{"description":"Lists supported US states and, when state is supplied, recorded city/ZIP labels and their counts. City labels do not represent polygons.","parameters":[{"description":"Optional state name/code.","in":"query","name":"state","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"cities":{"items":{"properties":{"count":{"type":"integer"},"value":{"type":"string"}},"type":"object"},"type":"array"},"postalCodes":{"items":{"properties":{"count":{"type":"integer"},"value":{"type":"string"}},"type":"object"},"type":"array"},"states":{"items":{"properties":{"code":{"type":"string"},"name":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Result"},"400":{"description":"Invalid scope, parameters, or missing boundary."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"List geocoding scope options","tags":["Internal staff geocoding"],"x-auth":"Current staff or ADMIN operator identity. Intelligence uses its signed session cookie; cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/geocoding/regions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"state=WA\"","title":"List recorded city and ZIP scope options in Washington"}],"x-internal":true}},"/api/ingestion/geocoding/status":{"get":{"description":"Fresh worker heartbeat, credential configuration, and reference availability, independent of the coverage aggregation. Does not scan catalog entities or call a provider.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"reference":{"properties":{"available":{"type":"boolean"},"source":{"type":"string"},"version":{"type":"string"}},"type":"object"},"worker":{"properties":{"error":{"type":"string"},"geocodioConfigured":{"type":"boolean"},"lastSeenAt":{"format":"date-time","type":"string"},"status":{"type":"string"}},"type":"object"}},"required":["worker","reference"],"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Result"},"400":{"description":"Invalid scope, parameters, or missing boundary."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"503":{"description":"Coverage calculation timed out; retry after the calculation cooldown."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read geocoding worker and reference status","tags":["Internal staff geocoding"],"x-auth":"Current staff or ADMIN operator identity. Intelligence uses its signed session cookie; cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/geocoding/status\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read geocoding worker and reference status"}],"x-internal":true}},"/api/ingestion/inbox/publications":{"get":{"description":"Returns the approval queue: one row per site whose newest website publication (the head that supersession uses) still has a pending batch, oldest waiting first. A batch is pending while it awaits approval or, released for some records by a decision, still has records awaiting one. kind=refresh keeps sites with an earlier publication that applied a record (approved whole or in part), kind=new the rest, kind=auto_approve and kind=has_flags the approval policy's verdicts; all is the default and any other value returns 400. limit (default 25, at most 100) and offset page it. total counts the rows matching kind; counts ignore it. Each row's approval is the website approval policy's verdict, null in mode off. Approve a row by approving its awaiting batches in order, or decide its records with POST /api/ingestion/inbox/publications/decision; a superseded publication returns 409. Excluded sites (GET /api/ingestion/excluded-sites) are left out of the rows and counts while excluded; their batches keep their status and the auto-approval sweep skips them.","parameters":[{"description":"Which sites to list.","in":"query","name":"kind","schema":{"type":"string","enum":["all","refresh","new","auto_approve","has_flags"]}},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100}},{"in":"query","name":"offset","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"The approval queue page.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/IngestionInbox"}}}}}},"400":{"description":"Invalid kind or paging."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN access required."}},"security":[{"ApiKeyAuth":[]}],"summary":"List website publications awaiting approval","tags":["Internal ingestion review"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/inbox/publications\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"kind=refresh\" \\\n  --data-urlencode \"limit=25\"","title":"List refreshes awaiting approval"}],"x-internal":true}},"/api/ingestion/inbox/publications/decision":{"post":{"description":"Approves and denies staged records of one website publication; records not named stay pending and keep the publication in the Inbox. An approved record's merge job moves on to mapping and merge as a whole-batch approval moves every job; the record keeps who approved it and when (origin_metadata.inbox_approval), and its batch records the operator as approver. A denied record's merge job ends rejected with the operator, time and reason; it never merges, live records are left unchanged, and a later scrape stages the record again. Denying an activity denies its pending sessions; denying a new site's organisation (neither approved earlier nor live) denies every pending record of the publication. Approving an activity needs the organisation approved now or earlier, or already live; approving a session needs its activity approved now or earlier, or already live. Every batch of the publication is then marked as decided by a person, and the website approval policy leaves the rest to people. A batch left with only denied records ends rejected; one with approved records completes once nothing is pending and its approved records settle. IDs compare case-insensitively. An approval's edits are checked as the staff edit form checks them before anything is written and apply as the reviewer's own change once the record merges unchanged. An overseer option queues one Overseer request for the publication's organisation once it merges.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestionInboxDecisionRequest"}}},"required":true},"responses":{"200":{"description":"The decided records and the publication's batches.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/IngestionInboxDecision"}}}}}},"400":{"description":"Invalid body; batch_ids not every batch of one website publication; a record not in the publication, already decided or named twice; a parent rule refused, naming the records; an edit the staff edit form refuses, naming the record, or edits from a reviewer without an email address; a NUL in an edit or the note; or an Overseer request for a publication that already asked, whose organisation is neither approved nor live, or whose text would exceed Overseer's message size."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN access required."},"404":{"description":"A batch was not found."},"409":{"description":"The publication is superseded by a newer one of its site, withdrawn by a reversal of any of its batches, being mapped for a batch the decision approves into (try again shortly), or changed during the decision."}},"security":[{"ApiKeyAuth":[]}],"summary":"Decide records of a website publication","tags":["Internal ingestion review"],"x-auth":"Current staff or ADMIN operator identity. Cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/inbox/publications/decision\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"batch_ids\":[\"ORGANIZATION_BATCH_UUID\",\"ACTIVITY_BATCH_UUID\",\"SESSION_BATCH_UUID\"],\"approve\":[{\"record_id\":\"RECORD_UUID\"}],\"deny\":[{\"record_id\":\"OTHER_RECORD_UUID\",\"reason\":\"Not a camp\"}],\"confirm\":true}'","title":"Approve one record and deny another"},{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/inbox/publications/decision\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"batch_ids\":[\"ORGANIZATION_BATCH_UUID\",\"ACTIVITY_BATCH_UUID\",\"SESSION_BATCH_UUID\"],\"approve\":[{\"record_id\":\"ORGANIZATION_RECORD_UUID\",\"edits\":{\"name\":\"Riverside Camps\"}}],\"overseer\":{\"note\":\"Session dates are on the Calendar page.\"},\"confirm\":true}'","title":"Approve the organisation with an edit and ask Overseer to fill its gaps"}],"x-internal":true}},"/api/ingestion/scraper-jobs":{"get":{"description":"Returns paginated durable scraper jobs. Filter by instanceId, case-insensitive website substring (websiteUrl is an alias), exact normalized websiteOrigin or websiteTarget, kind, status, exact failure code (error), and RFC3339 from/to creation bounds. websiteTarget matches all retained jobs for one exact normalized URL across installations; path case and query values stay distinct, while root slash/no-slash aliases share history when their queries match. websiteOrigin matches the scheme and host across page paths and queries; scheme, subdomains, www, and nondefault ports remain distinct. All supplied filters combine. Count and page share a read snapshot ordered by descending creation time then job ID; each page is a fresh read. A nonempty exact target filter is echoed as data.websiteTarget; clients requiring exact history must verify the normalized echo and report unavailable if absent or mismatched. Target history does not establish camp ownership. Unsupported queued work remains visible with its compatibility reason.","parameters":[{"description":"An absolute HTTP(S) URL without credentials, normalized with the queue's URL rules. Path, query, and fragment are ignored; root slashes and default ports normalize.","in":"query","name":"websiteOrigin","schema":{"format":"uri","maxLength":500,"type":"string"}},{"description":"One absolute HTTP(S) URL without credentials, at most 8192 decoded bytes. Queue normalization collapses repeated path slashes, trims non-root trailing slashes, sorts raw query pairs, and removes fragments. Root slash/no-slash aliases share exact history. The applied normalized URL is echoed as data.websiteTarget.","in":"query","name":"websiteTarget","schema":{"format":"uri","maxLength":8192,"type":"string"}},{"description":"An exact job failure code, such as training_mismatch, family_review_required, root_page_missing or crash_loop, at most 1000 characters. Combine with status=failed to list the jobs to retry after a fix.","in":"query","name":"error","schema":{"maxLength":1000,"type":"string"}},{"description":"summary adds each job's summary: its published batches' target kind, status and record count in approval order, and the coverage of its latest stored quality report (null when none is readable). It reads neither the job detail nor its artifacts. Any other value returns 400.","in":"query","name":"include","schema":{"type":"string","enum":["summary"]}},{"description":"active lists running jobs first, then queued, then the rest, each group newest created first. Omitted, the list is newest created first. Any other value returns 400.","in":"query","name":"order","schema":{"type":"string","enum":["active"]}}],"responses":{"200":{"description":"Paginated scraper jobs.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["jobs","page","pageSize","total"],"properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/ScraperJob"}},"total":{"type":"integer","minimum":0},"page":{"type":"integer","minimum":1},"pageSize":{"type":"integer","minimum":1},"websiteTarget":{"type":"string","description":"The normalized website target the list was filtered by, present only when websiteTarget was given."}}}}}}}},"400":{"description":"Invalid filter."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"List scraper jobs","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/scraper-jobs\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"websiteOrigin=https://www.example.org\" \\\n  --data-urlencode \"kind=refresh\" \\\n  --data-urlencode \"pageSize=25\"","title":"List refresh jobs for one website origin"}],"x-internal":true},"post":{"description":"Queues onboarding or refresh work for one installation. The API normalizes the URL without probing it, stamps contract version 2 when settings are supplied and version 1 otherwise, and attributes the request to the current operator. Settings are operator overrides for this job only (scope, capture limits, waits, and model budgets); followup refresh and repair jobs inherit them. Unknown or out-of-range settings are rejected. An optional whole-number priority (default 0) orders the installation's claims, higher first; retries and followups keep it. Repeating an identical idempotency key returns the original job as it is now, whatever priority the repeat names; other changed input conflicts.","requestBody":{"content":{"application/json":{"schema":{"properties":{"idempotencyKey":{"type":"string"},"instanceId":{"format":"uuid","type":"string"},"kind":{"enum":["onboarding","refresh","booking_inspection"]},"priority":{"description":"Claim order, higher first; 0 is normal and negative is low. Set by a person; automation submissions cannot name one.","maximum":2147483647,"minimum":-2147483648,"type":"integer"},"settings":{"additionalProperties":false,"properties":{"approved_external_origin":{"description":"An http or https origin captured as the site's one external origin.","format":"uri","type":"string"},"capture_timeout_ms":{"maximum":300000,"minimum":1000,"type":"integer"},"crawl_delay_ms":{"maximum":60000,"minimum":0,"type":"integer"},"dom_quiet_window_ms":{"maximum":30000,"minimum":1,"type":"integer"},"include_subdomains":{"type":"boolean"},"max_corpus_pages":{"maximum":5000,"minimum":1,"type":"integer"},"max_discovery_pages":{"maximum":5000,"minimum":1,"type":"integer"},"max_model_calls":{"maximum":1000,"minimum":1,"type":"integer"},"max_typesafe_requests":{"maximum":1000000,"minimum":1,"type":"integer"}},"type":"object"},"websiteUrl":{"format":"uri","type":"string"}},"required":["instanceId","websiteUrl","kind","idempotencyKey"],"type":"object"}}},"required":true},"responses":{"201":{"description":"Queued job, including an idempotent replay.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["job"],"properties":{"job":{"$ref":"#/components/schemas/ScraperJob"}}}}}}}},"400":{"description":"Invalid submission."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"409":{"description":"Idempotency key conflicts with a different submission, or the site is excluded; the message names the host and the exclusion reason. Booking inspections are not refused."}},"security":[{"ApiKeyAuth":[]}],"summary":"Queue a scraper job","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity. Cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/scraper-jobs\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"instanceId\":\"'\"$SCRAPER_INSTANCE_ID\"'\",\"websiteUrl\":\"https://www.example.org/summer\",\"kind\":\"refresh\",\"idempotencyKey\":\"refresh-example-org-2026-10-10\"}'","title":"Queue a website refresh"}],"x-internal":true}},"/api/ingestion/scraper-jobs/failure-codes":{"get":{"description":"Lists the distinct error values among failed scraper jobs with how many failed jobs carry each, most common first. Each code is a value the jobs list's error filter matches exactly. Some are failure codes such as page_status; others are operator notes recorded when a job was stopped.","responses":{"200":{"description":"Failure codes among failed jobs.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["codes"],"properties":{"codes":{"type":"array","items":{"$ref":"#/components/schemas/ScraperFailureCode"}}}}}}}}},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"List failure codes among failed scraper jobs","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/ingestion/scraper-jobs/failure-codes\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List failure codes"}],"x-internal":true}},"/api/ingestion/scraper-jobs/website-status":{"get":{"description":"Read-only batch status for exact normalized saved targets. Returns data.websites for each unique target in first-seen order, including never-scraped URLs. Each entry has website and nullable latest, completed and active jobs using the existing sanitized monitoring projection and unsupported-contract reason. Queue normalization lowercases scheme/host, removes default ports, collapses repeated path slashes, trims non-root trailing slashes, sorts raw query pairs and strips fragments. Remaining paths, query values, schemes, host aliases/subdomains and nondefault ports stay distinct. Root slash/no-slash forms share status when their queries match; job.website retains its exact stored value for refresh artifact ownership. Latest/completed use descending creation time then job ID; active prefers running over queued before that order. Invalid or over-limit batches return 400 without truncation. No scraper work is submitted.","parameters":[{"description":"Repeat for 1–50 absolute HTTP(S) URLs without credentials, each at most 8192 decoded bytes. No other query parameters are accepted.","explode":true,"in":"query","name":"websiteUrl","required":true,"schema":{"items":{"format":"uri","type":"string"},"maxItems":50,"minItems":1,"type":"array"},"style":"form"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"websites":{"items":{"properties":{"active":{"type":["object","null"]},"completed":{"type":["object","null"]},"latest":{"type":["object","null"]},"website":{"format":"uri","type":"string"}},"required":["website","latest","completed","active"],"type":"object"},"type":"array"}},"required":["websites"],"type":"object"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Exact website statuses."},"400":{"description":"Invalid or over-limit URL batch."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read exact website scrape statuses","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/scraper-jobs/website-status\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"websiteUrl=https://www.example.org/summer\" \\\n  --data-urlencode \"websiteUrl=https://camp.example.org/\"","title":"Read the scrape status of two saved websites"}],"x-internal":true}},"/api/ingestion/scraper-jobs/{id}":{"get":{"description":"Returns a durable job, attempts, sanitized events, linked batch IDs, and downstream website-publication status. Events from contract v3 workers may carry a redacted target URL, bounded redacted detail, model, operation, exit code, and named counters; job lists keep only the event summary. Scrape completion does not imply ingestion completion.","parameters":[{"description":"Return only events with an id above this one, for a reader that already holds the earlier events. event_count stays the job's total; events_truncated reports whether the newest 10,000 omit any newer events. Omitted or 0 returns the newest 10,000. Anything but a non-negative integer returns 400.","in":"query","name":"afterEvent","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"Scraper job detail.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/ScraperJobDetail"}}}}}},"400":{"description":"Invalid job ID."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"404":{"description":"Job not found."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read a scraper job","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/scraper-jobs/$SCRAPER_JOB_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read one scraper job"}],"x-internal":true}},"/api/ingestion/scraper-jobs/{id}/retry":{"post":{"description":"Queues a failed job again as a new job on the same installation, with the same website, kind, settings, attempt limit and priority, at the current job contract version, attributed to the current operator. Use it once the fix for the job's failure code is deployed. A failed job has at most one retry: the request returns 201 with the newly queued retry, and 200 with the earlier retry when one exists, whoever asked for it. A retry that fails too is retried by its own ID. The failed job stays unchanged.","responses":{"200":{"description":"The earlier retry of the same failed job; nothing new was queued.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["job"],"properties":{"job":{"$ref":"#/components/schemas/ScraperJob"}}}}}}}},"201":{"description":"The newly queued retry.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["job"],"properties":{"job":{"$ref":"#/components/schemas/ScraperJob"}}}}}}}},"400":{"description":"Invalid job ID."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"404":{"description":"Job not found."},"409":{"description":"The job has not failed, or its site is excluded; the message names the host and the exclusion reason."}},"security":[{"ApiKeyAuth":[]}],"summary":"Retry a failed scraper job","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity. Cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/scraper-jobs/$SCRAPER_JOB_ID/retry\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Retry a failed job"}],"x-internal":true}},"/api/ingestion/scraper-jobs/{id}/priority":{"post":{"description":"Changes the priority of a queued job by hand. An installation claims its highest-priority eligible queued job first, oldest first within a priority; 0 is normal and negative is low. Priorities are never computed, so a lower-priority job waits as long as higher-priority work keeps arriving. A running, paused, completed or failed job keeps its priority. Retries and followups of the job keep the priority it has when they are created.","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"priority":{"maximum":2147483647,"minimum":-2147483648,"type":"integer"}},"required":["priority"],"type":"object"}}},"required":true},"responses":{"200":{"description":"The queued job with its new priority.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["job"],"properties":{"job":{"$ref":"#/components/schemas/ScraperJob"}}}}}}}},"400":{"description":"Invalid job ID, or priority missing or not a whole number in range."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"404":{"description":"Job not found."},"409":{"description":"The job is not queued; its priority is unchanged."}},"security":[{"ApiKeyAuth":[]}],"summary":"Change a queued scraper job's priority","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity. Cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/scraper-jobs/$SCRAPER_JOB_ID/priority\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"priority\":10}'","title":"Run a queued job next"}],"x-internal":true}},"/api/ingestion/scraper-jobs/{id}/artifacts":{"get":{"description":"Returns the job's compact diagnostic artifacts, oldest first, with attempt, kind, key, encoded size, and an omission reason (too_large or attempt_budget) instead of a body when one was not stored. Bodies are read one at a time.","responses":{"200":{"description":"Artifact summaries.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["artifacts"],"properties":{"artifacts":{"type":"array","items":{"$ref":"#/components/schemas/ScraperArtifactSummary"}}}}}}}}},"400":{"description":"Invalid job ID."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"404":{"description":"Job not found."}},"security":[{"ApiKeyAuth":[]}],"summary":"List a scraper job's diagnostic artifacts","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/scraper-jobs/$SCRAPER_JOB_ID/artifacts\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List a job’s diagnostic artifacts"}],"x-internal":true}},"/api/ingestion/scraper-jobs/{id}/artifacts/{artifactId}":{"get":{"description":"Returns one artifact of the job with its JSON body. Artifacts may contain page URLs, extracted values, model node selections, generated extractor source, and bounded model output; never captured page bodies, the annotated DOM, or credentials.","responses":{"200":{"description":"Artifact with body.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["artifact"],"properties":{"artifact":{"$ref":"#/components/schemas/ScraperArtifact"}}}}}}}},"400":{"description":"Invalid job or artifact ID."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."},"404":{"description":"Job or artifact not found."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read a scraper diagnostic artifact","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/scraper-jobs/$SCRAPER_JOB_ID/artifacts/$ARTIFACT_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read one diagnostic artifact body"}],"x-internal":true}},"/api/ingestion/scrapers":{"get":{"description":"Returns scraper installations, current sessions, connection state, every running job (current_jobs, oldest first; current_job is the first), the shared Codex subscription each live session leases (codex_account: id, label, email, or null), each installation's build (revision, and main_revision, behind_main and checked_at as its server last reported them, or null), its newest dashboard update request (update, or null; a request running for over 15 minutes without a result is shown as failed with message no result), and the job-contract revisions accepted by this API process.","responses":{"200":{"description":"Installations and contract compatibility.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["instances","contractVersion","supportedVersions"],"properties":{"instances":{"type":"array","items":{"$ref":"#/components/schemas/ScraperInstance"}},"contractVersion":{"type":"integer"},"supportedVersions":{"type":"array","items":{"type":"integer"}}}}}}}}},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"List scraper installations","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/scrapers\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List scraper installations"}],"x-internal":true}},"/api/ingestion/scrapers/{id}/update":{"post":{"description":"Queues an update to the main revision the installation's server last reported (build.main_revision). The installation's own worker drains its jobs (about 10 minutes at most, otherwise the request is refused as busy), then hands the request to the server's root-owned update script. The script re-validates it (the target must be on a freshly fetched main), runs the new build's database check (refused with the missing migration otherwise), installs it, and rolls back on failure. The next worker records the result: data.request moves through pending, draining and running to succeeded, rolled_back, failed or refused, with from_revision, to_revision, and a redacted message and log_tail. Responses never contain server paths or secrets.","responses":{"202":{"description":"data.request in pending.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"data":{"type":"object","additionalProperties":false,"properties":{"request":{"$ref":"#/components/schemas/ScraperUpdateRequest"}},"required":["request"]},"success":{"type":"boolean"}},"required":["data","success"]}}}},"400":{"description":"Invalid ID."},"404":{"description":"Unknown installation."},"409":{"description":"The installation already has an active update request, or its server has not reported the main revision."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"Update a scraper installation to main","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/scrapers/$SCRAPER_INSTALLATION_ID/update\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Update to main"}],"x-internal":true,"parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"},"description":"Scraper installation ID."}]}},"/api/ingestion/scrapers/{id}/rollback":{"post":{"description":"Queues a rollback to the installation's previous build; it runs like an update, with target_revision null.","responses":{"202":{"description":"data.request in pending.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"data":{"type":"object","additionalProperties":false,"properties":{"request":{"$ref":"#/components/schemas/ScraperUpdateRequest"}},"required":["request"]},"success":{"type":"boolean"}},"required":["data","success"]}}}},"400":{"description":"Invalid ID."},"404":{"description":"Unknown installation."},"409":{"description":"The installation already has an active update request."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"Roll a scraper installation back","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/scrapers/$SCRAPER_INSTALLATION_ID/rollback\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Roll back"}],"x-internal":true,"parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"},"description":"Scraper installation ID."}]}},"/api/ingestion/codex-accounts":{"get":{"description":"Returns data.accounts: the team's Codex (ChatGPT) subscriptions that are not removed, with status, the device link and code while a login waits for approval, usage percentages and reset time, the leasing worker, and a redacted last error. Never returns tokens, auth.json contents, or filesystem paths.","responses":{"200":{"description":"The subscription pool.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["accounts"],"properties":{"accounts":{"type":"array","items":{"$ref":"#/components/schemas/ScraperCodexAccount"}}}}}}}}},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"List shared Codex subscriptions","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/codex-accounts\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List shared Codex subscriptions"}],"x-internal":true},"post":{"description":"Adds a subscription labelled 1-80 characters (for example the teammate's name) in pending_login. A worker runs codex login --device-auth for it and publishes device_url, user_code and code_expires_at for the teammate to approve.","responses":{"201":{"description":"data.account in pending_login.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["account"],"properties":{"account":{"$ref":"#/components/schemas/ScraperCodexAccount"}}}}}}}},"400":{"description":"Invalid label."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"Add a shared Codex subscription","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/ingestion/codex-accounts\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H 'content-type: application/json' \\\n  -d '{\"label\":\"Alex\"}'","title":"Add a subscription"}],"x-internal":true}},"/api/ingestion/codex-accounts/{id}/remove":{"post":{"description":"Marks the subscription removing. The worker holding it (or any worker when none holds it) stops using it, deletes the server's login directory, and marks it removed. It never logs the subscription out or revokes its tokens, so the teammate stays signed in elsewhere.","responses":{"202":{"description":"data.account in removing.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["account"],"properties":{"account":{"$ref":"#/components/schemas/ScraperCodexAccount"}}}}}}}},"400":{"description":"Invalid ID."},"404":{"description":"Unknown or already removed subscription."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"Remove a shared Codex subscription","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/codex-accounts/$CODEX_ACCOUNT_ID/remove\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Remove a subscription"}],"x-internal":true,"parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"},"description":"Codex subscription ID."}]}},"/api/ingestion/codex-accounts/{id}/usage-ceiling":{"post":{"description":"Sets usage_ceiling_pct, a whole percent from 1 to 100, or clears it with null. The ceiling applies to the total reported use of either window (five-hour or weekly), including the owner's own use outside Trident. At or above it, workers lease no new calls on the subscription, calls already running finish, and the worker switches to another subscription (or pauses on Codex when none is usable); the subscription keeps its login. It returns once a fresh usage reading after its window resets is below the ceiling, or at once when the ceiling is raised above current use or cleared. Allowed in any status before removal, including before sign-in completes. A small overshoot is possible: readings are at most about a minute old and running calls finish.","responses":{"200":{"description":"data.account with the new usage_ceiling_pct.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["account"],"properties":{"account":{"$ref":"#/components/schemas/ScraperCodexAccount"}}}}}}}},"400":{"description":"Invalid ID, or usage_ceiling_pct missing, not a whole number from 1 to 100 and not null, or sent with other fields."},"404":{"description":"Unknown or removed subscription."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"security":[{"ApiKeyAuth":[]}],"summary":"Set or clear a shared Codex subscription's usage ceiling","tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/codex-accounts/$CODEX_ACCOUNT_ID/usage-ceiling\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"usage_ceiling_pct\":50}'","title":"Stop using a subscription at 50% of either window"}],"x-internal":true,"parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"},"description":"Codex subscription ID."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["usage_ceiling_pct"],"properties":{"usage_ceiling_pct":{"type":["integer","null"],"minimum":1,"maximum":100}}},"example":{"usage_ceiling_pct":50}}}}}},"/api/ingestion/excluded-sites":{"get":{"security":[{"ApiKeyAuth":[]}],"tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-internal":true,"summary":"List excluded sites","description":"Pages excluded sites newest first (created_at, then id). Each has its creator's email and live_data: the canonical organisations on its host now, computed when read. Removed (un-excluded) sites are listed only with include=removed.","parameters":[{"in":"query","name":"q","description":"Case-insensitive text in the host, URL or comment; at most 200 characters.","schema":{"type":"string","maxLength":200}},{"in":"query","name":"include","description":"removed also lists un-excluded sites; any other value returns 400.","schema":{"type":"string","enum":["removed"]}},{"in":"query","name":"limit","description":"Default 50, at most 100.","schema":{"type":"integer","minimum":1,"maximum":100}},{"in":"query","name":"offset","description":"At most 10000.","schema":{"type":"integer","minimum":0,"maximum":10000}}],"responses":{"200":{"description":"One page and the count of every match.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["sites","total","limit","offset"],"properties":{"sites":{"type":"array","items":{"$ref":"#/components/schemas/ScraperExcludedSite"}},"total":{"type":"integer","minimum":0},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}}}},"400":{"description":"Invalid include, q or offset."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/excluded-sites\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode 'q=youth' --data-urlencode 'include=removed'","title":"Search excluded sites, removed ones included"}]},"post":{"security":[{"ApiKeyAuth":[]}],"tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-internal":true,"summary":"Exclude a site","description":"Excludes the site's host (lowercased, no scheme, port or path, leading www. stripped; a bare host is read as https). A new exclusion fails the host's queued and paused scraper jobs, booking inspections aside, with error website_excluded in the same transaction and returns 201; running jobs finish but queue no followups, and a claim fails them if they are queued again. When the host is already excluded, returns 200 with that exclusion unchanged. Live data is never changed: hide an organisation with POST /api/organizations/{id}/blacklist.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["url","reason_code","source"],"properties":{"url":{"type":"string","maxLength":2048,"description":"A website address or host."},"reason_code":{"type":"string","description":"A code from GET /api/ingestion/excluded-sites/reasons; an unknown code returns 400."},"comment":{"type":["string","null"],"maxLength":2000,"description":"At most 2,000 characters. Required when reason_code is other."},"source":{"type":"string","enum":["inbox","scraper_job","exclusions_page"]},"source_id":{"type":["string","null"],"maxLength":200,"description":"The scraper job or batch the site was excluded from."},"revisit_at":{"type":["string","null"],"format":"date-time","description":"When staff mean to look at the site again; informational."}}},"example":{"url":"https://www.youth-shelter.example/programs","reason_code":"not_camp_provider","comment":"Shelter, not camps.","source":"inbox","source_id":"6b1f2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d"}}}},"responses":{"201":{"description":"The new exclusion and how many queued or paused jobs it failed.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["site","created","cancelled_job_count"],"properties":{"site":{"$ref":"#/components/schemas/ScraperExcludedSite"},"created":{"const":true},"cancelled_job_count":{"type":"integer","minimum":0}}}}}}}},"200":{"description":"The host's existing active exclusion, unchanged.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["site","created","cancelled_job_count"],"properties":{"site":{"$ref":"#/components/schemas/ScraperExcludedSite"},"created":{"const":false},"cancelled_job_count":{"const":0}}}}}}}},"400":{"description":"Invalid url, unknown reason_code, other without a comment, invalid source or revisit_at, or an unknown field."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/ingestion/excluded-sites\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H 'content-type: application/json' \\\n  -d '{\"url\":\"youth-shelter.example\",\"reason_code\":\"not_camp_provider\",\"source\":\"exclusions_page\"}'","title":"Exclude a site"}]}},"/api/ingestion/excluded-sites/reasons":{"get":{"security":[{"ApiKeyAuth":[]}],"tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-internal":true,"summary":"List exclusion reasons","description":"The registered reason codes, in the order to offer them. Codes are defined in Go (ingestion.ExclusionReasons), not the database.","responses":{"200":{"description":"The reasons.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["reasons"],"properties":{"reasons":{"type":"array","items":{"$ref":"#/components/schemas/ScraperExcludedSiteReason"}}}}}}}}},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/ingestion/excluded-sites/reasons\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List reasons"}]}},"/api/ingestion/excluded-sites/lookup":{"get":{"security":[{"ApiKeyAuth":[]}],"tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-internal":true,"summary":"Look up whether sites are excluded","description":"For tags on scraper pages: one entry per url or host sent, in order, saying whether its host has an active exclusion. An entry that is not a website address or host (or is over 2048 bytes) comes back as {input, host: \"\", excluded: false, site: null}. 1-200 sites in total; no sites, more than 200, or any other parameter returns 400.","parameters":[{"in":"query","name":"url","description":"Repeat for each website address.","style":"form","explode":true,"schema":{"type":"array","items":{"type":"string"},"maxItems":200}},{"in":"query","name":"hosts","description":"Comma-separated hosts.","schema":{"type":"string"}}],"responses":{"200":{"description":"One entry per site sent.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["sites"],"properties":{"sites":{"type":"array","items":{"$ref":"#/components/schemas/ScraperExcludedSiteLookup"}}}}}}}}},"400":{"description":"No sites, more than 200, or an unknown parameter."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/ingestion/excluded-sites/lookup\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode 'url=https://www.youth-shelter.example/programs' --data-urlencode 'hosts=camp.example,other.example'","title":"Tag sites"}]}},"/api/ingestion/excluded-sites/{id}/remove":{"post":{"security":[{"ApiKeyAuth":[]}],"tags":["Internal scraper monitoring"],"x-auth":"Current staff or ADMIN operator identity.","x-internal":true,"summary":"Un-exclude a site","description":"Sets removed_at, removed_by and the optional removal comment; the exclusion stays listed with include=removed. Scraping, followups, the Inbox and approval work for the host again. Jobs the exclusion failed stay failed: retry them. Removing an already removed exclusion returns it unchanged.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"},"description":"Excluded site ID."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"comment":{"type":["string","null"],"maxLength":2000,"description":"At most 2,000 characters."}}},"example":{"comment":"They run summer camps now."}}}},"responses":{"200":{"description":"The removed exclusion.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["site"],"properties":{"site":{"$ref":"#/components/schemas/ScraperExcludedSite"}}}}}}}},"400":{"description":"Invalid ID, comment or an unknown field."},"404":{"description":"Unknown excluded site."},"401":{"description":"Authentication required."},"403":{"description":"Current identity lacks staff/ADMIN operator access."}},"x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/excluded-sites/$EXCLUDED_SITE_ID/remove\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H 'content-type: application/json' \\\n  -d '{\"comment\":\"They run summer camps now.\"}'","title":"Un-exclude a site"}]}},"/api/insights":{"get":{"description":"Lists the datasets your key can access, largest first. Top-level capabilities are the tier-filtered union available across those datasets; each dataset also reports only the exact filters, searchable fields, sortable fields, and dynamic filter that its columns and your plan support. The catalog accepts no query parameters.","responses":{"200":{"content":{"application/json":{"example":{"data":{"capabilities":{"dynamicFilter":"data.<key>","exactFilters":["source","record_type","category","provider","name","state","city","county","country","domain","external_id","parent_external_id","status"],"searchable":["name","provider","category","description","city","county","state","country"],"sortable":["created_at","ingested_at","name","rating","review_count","amount_cents","starts_at","state","city","source"]},"datasetCount":2,"datasets":[{"capabilities":{"dynamicFilter":"data.<key>","exactFilters":["source","record_type","category","provider","name","state","city","county","country","domain","external_id","parent_external_id","status"],"searchable":["name","provider","category","description","city","county","state","country"],"sortable":["created_at","ingested_at","name","rating","review_count","amount_cents","starts_at","state","city","source"]},"dataset":"booking_platform_census","endpoint":"/api/insights/booking_platform_census","rows":26805,"sources":[{"rows":26805,"source":"booking_fingerprint"}]}],"endpoints":{"catalog":"/api/insights","dataset":"/api/insights/{dataset}?q=&{exactFilter}=&data.<key>=&limit=&offset=&orderBy=&order=&format=csv","search":"/api/insights/search?q=&dataset=&{exactFilter}=&limit=&offset="},"tier":"enterprise","totalRows":30970},"success":true}}},"description":"Datasets, row counts, sources, query capabilities, and endpoint paths"},"400":{"content":{"application/json":{"example":{"data":null,"error":true,"message":"The insights catalog does not accept query parameters."}}},"description":"The catalog received a query parameter."},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List datasets","tags":["Data and insights"],"x-auth":"Administrator account or named integration key owned by an administrator. Legacy Insights data is ADMIN-only; ordinary paid plans do not grant access.","x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/insights\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Catalog"}]}},"/api/insights/search":{"get":{"description":"Searches available text fields across every dataset your key can access, or one dataset selected with dataset. Up to five q values are matched in one ranked request. Exact, prefix, and substring matches are relevance-ranked; ties are ordered by name, dataset, then UUID. Exact filters narrow the results; a cross-dataset search skips datasets without every requested filter. Each non-q parameter may appear once, and unknown, blank, excessive, repeated, or malformed parameters return 400. A busy search service returns 429 with a one-second retry delay.","parameters":[{"description":"One to five required search terms, trimmed and deduplicated before use. Searches the visible fields a dataset carries from: name, provider, category, description, city, county, state, and country. A record may match any term. Matching is case-insensitive. Exact matches rank above prefixes, then other substrings. Earlier fields carry more weight among the searchable fields available on that dataset. Every hit includes a score; higher scores are more relevant. %, _, and ! are matched literally, not treated as pattern syntax.","explode":true,"in":"query","name":"q","required":true,"schema":{"example":["camp fire","robotics"],"items":{"maxLength":200,"minLength":1,"type":"string"},"maxItems":5,"minItems":1,"type":"array"},"style":"form"},{"description":"Search only this case-insensitive dataset identifier from GET /api/insights. An unknown dataset returns 404; a dataset unavailable to your plan returns 403.","in":"query","name":"dataset","required":false,"schema":{"example":"atlanta_camps","type":"string"}},{"description":"Rows per page. Must be a whole number from 1 through 200; invalid values return 400. Defaults to 50. Free accounts receive 403: the Data Explorer opens with the Starter plan.","in":"query","name":"limit","required":false,"schema":{"default":50,"maximum":200,"minimum":1,"type":"integer"}},{"description":"Zero-based row offset. Must be a whole number from 0 through 10,000; invalid values return 400. When hasMore is true, use nextOffset for the following page. Free accounts receive 403: the Data Explorer opens with the Starter plan.","in":"query","name":"offset","required":false,"schema":{"default":0,"maximum":10000,"minimum":0,"type":"integer"}},{"description":"Case-sensitive exact source match.","in":"query","name":"source","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact record type match.","in":"query","name":"record_type","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact category match.","in":"query","name":"category","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact provider match. A plan without provider access receives 403.","in":"query","name":"provider","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact name match. q still determines which records are searched and ranked.","in":"query","name":"name","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact state match, usually a two-letter abbreviation.","in":"query","name":"state","required":false,"schema":{"example":"GA","type":"string"}},{"description":"Case-sensitive exact city match.","in":"query","name":"city","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact county match.","in":"query","name":"county","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact country match.","in":"query","name":"country","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact domain match. A plan without business identity access receives 403.","in":"query","name":"domain","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact source-system identifier match.","in":"query","name":"external_id","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact parent source-system identifier match.","in":"query","name":"parent_external_id","required":false,"schema":{"type":"string"}},{"description":"Case-sensitive exact status match.","in":"query","name":"status","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"data":{"count":1,"hasMore":true,"hits":[{"category":"Camp","dataset":"atlanta_camps","id":"00000000-0000-0000-0000-000000000000","name":"Atlanta Art Camp","record_type":"provider","score":80,"source":"atlanta_remys"}],"limit":25,"nextOffset":25,"offset":0,"q":"art camp","queries":["art camp"],"tier":"paid"},"success":true}}},"description":"A relevance-ranked page with limit, offset, hasMore, nextOffset, and a score on every hit. Free accounts receive 403: the Data Explorer opens with the Starter plan."},"400":{"$ref":"#/components/responses/InvalidSearchQuery"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/SearchBusy"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/SearchTimeout"}},"summary":"Search datasets","tags":["Data and insights"],"x-auth":"Administrator account or named integration key owned by an administrator. Legacy Insights data is ADMIN-only; ordinary paid plans do not grant access.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/insights/search\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"q=art camp\" \\\n  --data-urlencode \"q=robotics\" \\\n  --data-urlencode \"state=GA\" \\\n  --data-urlencode \"limit=25\" \\\n  --data-urlencode \"offset=0\"","title":"Search and filter every visible dataset"},{"code":"curl --get \"https://apis.schoolsoutapp.com/api/insights/search\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"q=stem\" \\\n  --data-urlencode \"dataset=atlanta_camps\" \\\n  --data-urlencode \"category=Camp\"","title":"Search one dataset"}]}},"/api/insights/{dataset}":{"get":{"description":"Filters and pages one dataset. Each q searches name, and up to five q values are joined with OR in one request. Named filters and data.{key} use case-sensitive exact matches, and multiple filters are joined with AND. Unknown parameters, blank values, malformed controls, too many q values, and repeated format, limit, offset, orderBy, or order parameters return 400.","parameters":[{"description":"A dataset identifier from GET /api/insights, such as atlanta_camps. Identifiers are case-insensitive.","in":"path","name":"dataset","required":true,"schema":{"example":"atlanta_camps","type":"string"}},{"description":"Searches the name column only. Repeat q up to five times to match any term in one request. Matching is case-insensitive and substring-based. It does not search category, provider, location, or data. % matches any sequence and _ matches one character; use cross-dataset search when those characters must be treated literally.","explode":true,"in":"query","name":"q","required":false,"schema":{"example":["art camp","robotics"],"items":{"maxLength":200,"minLength":1,"type":"string"},"maxItems":5,"minItems":1,"type":"array"},"style":"form"},{"description":"An exact, case-sensitive source value. Check /api/insights/{dataset}/facets or the catalog’s sources list for valid values.","in":"query","name":"source","required":false,"schema":{"example":"booking_fingerprint","type":"string"}},{"description":"An exact, case-sensitive record type. Only some datasets include this field.","in":"query","name":"record_type","required":false,"schema":{"type":"string"}},{"description":"An exact, case-sensitive category. This does not accept substrings or a list of values.","in":"query","name":"category","required":false,"schema":{"example":"STEM","type":"string"}},{"description":"An exact provider value. This filter is available only when your plan includes the provider field.","in":"query","name":"provider","required":false,"schema":{"type":"string"}},{"description":"An exact, case-sensitive name. Use q to search within a name.","in":"query","name":"name","required":false,"schema":{"example":"Camp Wildwood","type":"string"}},{"description":"An exact state value, usually a two-letter abbreviation. Full state names are not converted for you.","in":"query","name":"state","required":false,"schema":{"example":"GA","type":"string"}},{"description":"An exact, case-sensitive city.","in":"query","name":"city","required":false,"schema":{"example":"Atlanta","type":"string"}},{"description":"An exact, case-sensitive county.","in":"query","name":"county","required":false,"schema":{"type":"string"}},{"description":"An exact, case-sensitive country.","in":"query","name":"country","required":false,"schema":{"type":"string"}},{"description":"An exact domain. This filter is available only when your plan includes business identity fields.","in":"query","name":"domain","required":false,"schema":{"example":"example.org","type":"string"}},{"description":"An exact identifier from the source system.","in":"query","name":"external_id","required":false,"schema":{"type":"string"}},{"description":"An exact parent identifier from the source system.","in":"query","name":"parent_external_id","required":false,"schema":{"type":"string"}},{"description":"An exact, case-sensitive status.","in":"query","name":"status","required":false,"schema":{"type":"string"}},{"description":"Matches one top-level data value as exact text. Replace {key} with the JSON key, for example data.facility_type=Day Camp. Keys may contain letters, numbers, underscores, periods, hyphens, and spaces. A key hidden by your plan returns 403.","example":"data.facility_type=Day Camp","in":"query","name":"data.{key}","required":false,"schema":{"type":"string"}},{"description":"Rows per page. Must be a whole number from 1 through 500 for JSON or 1 through 5,000 for CSV; invalid values return 400. Defaults to 50 for JSON and 500 for CSV. Free accounts receive 403: the Data Explorer opens with the Starter plan.","in":"query","name":"limit","required":false,"schema":{"default":50,"example":50,"maximum":5000,"minimum":1,"type":"integer"}},{"description":"Zero-based row offset. Must be a non-negative whole number; invalid values return 400. Free accounts receive 403: the Data Explorer opens with the Starter plan.","in":"query","name":"offset","required":false,"schema":{"default":0,"example":100,"minimum":0,"type":"integer"}},{"description":"Column used for sorting. Must be one of the listed fields and available on the dataset; unsupported values return 400, while a field blocked by your plan returns 403. Defaults to created_at. Nulls sort last.","in":"query","name":"orderBy","required":false,"schema":{"default":"created_at","enum":["created_at","ingested_at","name","rating","review_count","amount_cents","starts_at","state","city","source"],"type":"string"}},{"description":"Sort direction. Accepts asc or desc, without regard to case; any other value returns 400. Defaults to desc.","in":"query","name":"order","required":false,"schema":{"default":"desc","enum":["asc","desc"],"type":"string"}},{"description":"Set to csv, without regard to case, to download one CSV page. Any other value returns 400. CSV uses the same filters, field access, sorting, and offset as JSON, with a 5,000-row maximum page size.","in":"query","name":"format","required":false,"schema":{"enum":["csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"data":{"count":50,"dataset":"atlanta_camps","hasMore":true,"limit":50,"offset":0,"order":"asc","orderBy":"name","rows":[],"tier":"paid","total":52},"success":true}}},"description":"A page of matching rows, or a CSV download. JSON includes total, count, limit, offset, and hasMore. Free accounts receive 403: the Data Explorer opens with the Starter plan."},"400":{"$ref":"#/components/responses/InvalidDatasetQuery"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Filter a dataset","tags":["Data and insights"],"x-auth":"Administrator account or named integration key owned by an administrator. Legacy Insights data is ADMIN-only; ordinary paid plans do not grant access.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/insights/atlanta_camps\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"q=art camp\"","title":"Name contains words"},{"code":"curl --get \"https://apis.schoolsoutapp.com/api/insights/atlanta_camps\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"q=camp\" \\\n  --data-urlencode \"state=GA\" \\\n  --data-urlencode \"category=STEM\" \\\n  --data-urlencode \"orderBy=name\" \\\n  --data-urlencode \"order=asc\" \\\n  --data-urlencode \"limit=50\"","title":"Combine exact filters with q"},{"code":"curl --get \"https://apis.schoolsoutapp.com/api/insights/national_camp_lists\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"data.facility_type=Day Camp\"","title":"Filter a JSON key"},{"code":"curl --get \"https://apis.schoolsoutapp.com/api/insights/atlanta_camps\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"state=GA\" \\\n  --data-urlencode \"format=csv\" \\\n  -o atlanta-camps.csv","title":"Download the filtered rows"}]}},"/api/insights/{dataset}/facets":{"get":{"description":"Lists common values and row counts for source, record_type, category, provider, status, state, county, city, and country when those fields are visible. The response includes up to 50 values per field. Free accounts receive 403: the Data Explorer opens with the Starter plan. This endpoint accepts no query parameters.","parameters":[{"description":"A dataset slug from GET /api/insights.","in":"path","name":"dataset","required":true,"schema":{"example":"atlanta_camps","type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"data":{"dataset":"atlanta_camps","facets":{"state":[{"rows":52,"value":"GA"}]},"tier":"paid"},"success":true}}},"description":"Filter values and row counts"},"400":{"$ref":"#/components/responses/NoQueryParameters"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"List filter values","tags":["Data and insights"],"x-auth":"Administrator account or named integration key owned by an administrator. Legacy Insights data is ADMIN-only; ordinary paid plans do not grant access.","x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/insights/atlanta_camps/facets\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Available exact filters"}]}},"/api/insights/{dataset}/{id}":{"get":{"description":"Returns one record by its School’s Out UUID. This endpoint accepts no query parameters.","parameters":[{"description":"A dataset slug from GET /api/insights.","in":"path","name":"dataset","required":true,"schema":{"example":"atlanta_camps","type":"string"}},{"description":"The UUID in the record’s id field. Source-system identifiers do not work here.","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"One record"},"400":{"$ref":"#/components/responses/InvalidRowId"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Get a record","tags":["Data and insights"],"x-auth":"Administrator account or named integration key owned by an administrator. Legacy Insights data is ADMIN-only; ordinary paid plans do not grant access.","x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/insights/atlanta_camps/ROW_UUID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"One row"}]}},"/api/organizations":{"get":{"description":"Filters, sorts, and pages the canonical organization catalog. canonical_url is the verified official primary source. relevant_urls is an array of supporting or unverified pages; inclusion does not establish an official site. Both URL fields require Team data access. JSON rows omit scores and list reads do not load score summaries or calculate scores. minimumScore and maximumScore are inclusive whole-number bounds; score filtering and ordering happen across the complete cohort selected by the ordinary organization filters before pagination. For filtered or score-ordered queries, total is the exact number of matching organizations. Unfiltered total and unfilteredTotal use durable incrementally maintained totals after relevant background work settles and fall back to live counts while committed changes are pending. Returned rows and access checks remain live. Score filters, counts, and ordering use persisted results refreshed by workers after evidence changes and roughly hourly for time-based decay. Pausing workers retains cached scores and their evaluation times without preventing API reads. The separate cached score batch endpoint reads the same persisted results; calculatedAt reports their stored evaluation time. Reads do not load score evidence or evaluate decay. Organization rows without prepared scores remain visible unless excluded by a score filter or score ordering. Returns retryable 503 until score objects are installed. To count scores strictly above 80, send minimumScore=81 and limit=1. ageYears uses whole completed years. The legacy ageMonths filter remains available; month-shaped aliases derived from canonical inclusive year bounds use [yearMin*12,yearMax*12+11]. collectionId is staff-only; collections contain organizations and do not infer activity or session category labels. Each ordinary JSON row includes image_urls, an array of its own active photo URLs (gallery images; a logo is never a photo), ordered by selected cover, then gallery position. A family list row (the family projection, without fields=pins or showRemoved) reads its media in the list statement: image_urls holds at most its first photo (selected cover, else first gallery photo), or is empty; every other read keeps every photo. An entity without a photo returns an empty array, and a reader fills the slot itself. An organization row with a selected logo also includes logo_url, its display URL; the field is omitted when there is none. Remote URLs are preserved verbatim; uploaded images retain protected organization brand-asset URLs. logo_url is the exception: once the selected logo has a rendition (uploaded logos at upload, remote ones after the ingestion worker mirrors them), it is the absolute URL of that rendition on the public logo route (GET /api/media/logos/{file}), which any origin can load and read without credentials; before that it is the logo’s source URL. Documents, deleted or misowned uploads, inactive memberships, other entities’ media, and source provenance are excluded. Investor previews omit image_urls and logo_url, CSV columns remain unchanged, and includeRows=false skips media reads. Normal JSON records omit bookkeeping and ingestion history, null and blank record fields, and unknown availability, registration-status, and coordinate-precision values. Zero, false, empty arrays, masked values, positional range bounds, and structured schedule states retain their meanings. Related entity summaries follow the same projection; CSV retains its existing contract. Bookkeeping and provenance are available separately at /api/organizations/{id}/provenance. Age ranges use age_range_years; normal JSON records and column metadata omit the redundant age_range_months alias. CSV and the legacy ageMonths filter retain month compatibility. Records and column metadata omit score. Read /api/organizations/{id}/score for live detail or /api/organizations/scores?ids=UUID,UUID&cached=true for persisted summaries. A consumer's own signed-in account reads at any tier, from sign-up on with the family fields added to its tier's (FamilyCatalogRecord fields; projection: family) and may filter by near, radiusMiles and bbox; other family offering filters return 400 here. format=csv returns 403 when the tier itself may not read organizations. The structured filter JSON combines with legacy parameters using AND. All branches validate against current field disclosure; see CanonicalFilter and /api/canonical/schema. Ordinary permitted JSON records include resolved_cover with the ultimate source entity, inheritance flag, and structured stock attribution. Owned gallery and image compatibility fields retain their existing meaning.","parameters":[{"description":"Case-insensitive substring search across organization name, description, city, region, and age keyword. Repeat up to five times to match any term.","explode":true,"in":"query","name":"q","required":false,"schema":{"items":{"maxLength":200,"minLength":1,"type":"string"},"maxItems":5,"minItems":1,"type":"array"},"style":"form"},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): words that must each match whole. Every word of every value (values split at whitespace) must be in the record's family search fields (the search fields a family sees, as a family sees them, so hidden text never matches) as a whole word or a word of the same English stem, case-insensitively (art matches Arts but not party; camps matches camp); a value's words may match in different fields. Stop words (the, for) are ignored, so a value of stop words alone keeps every row. ANDed with contains, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters; at most 10 words in all. For partial words, send contains.","in":"query","name":"text","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): text that each must contain. Each value matches when any visible search field contains it anywhere, case-insensitively, as one q value does (art matches Arts and party), as the reader sees the field, so hidden text never matches. ANDed with each other, with text, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters. Very common fragments of a few letters are the slowest searches.","in":"query","name":"contains","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Case-insensitive exact city name or its lowercase hyphenated slug.","in":"query","name":"city","required":false,"schema":{"example":"seattle","type":"string"}},{"description":"Case-insensitive exact region, such as a state abbreviation.","in":"query","name":"region","required":false,"schema":{"example":"WA","type":"string"}},{"description":"Exact category recorded on the organization itself; categories are independent across organizations, activities, and sessions.","in":"query","name":"category","required":false,"schema":{"example":"STEM","type":"string"}},{"description":"Legacy compatibility filter for month-based ages. New clients should use whole-year ageYears; when a whole-year range is sent through a legacy month-shaped adapter, inclusive bounds expand as [yearMin*12,yearMax*12+11].","in":"query","name":"ageMonths","required":false,"schema":{"example":120,"maximum":100000,"minimum":0,"type":"integer"}},{"description":"Returns organizations whose stated grade range includes this grade. Use -1 for pre-K and 0 for kindergarten.","in":"query","name":"grade","required":false,"schema":{"example":5,"maximum":12,"minimum":-1,"type":"integer"}},{"description":"Inclusive minimum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response.","in":"query","name":"minimumScore","required":false,"schema":{"example":81,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Inclusive maximum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response. Must not be below minimumScore.","in":"query","name":"maximumScore","required":false,"schema":{"example":100,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Sort by score, name, city, region, created_at, or updated_at. Without orderBy, the catalog uses updated_at descending. In the family projection, distance (with near) orders nearest first by great-circle distance, rows without coordinates last (distanceMiles is the spheroid distance, so rows within about 0.5% of each other may list in either order); family organization reads otherwise order by name.","in":"query","name":"orderBy","required":false,"schema":{"enum":["score","name","city","region","created_at","updated_at"],"type":"string"}},{"description":"Sort direction. An explicit orderBy uses ascending order when this is omitted.","in":"query","name":"order","required":false,"schema":{"enum":["asc","desc"],"type":"string"}},{"description":"One-based page number. A missing or invalid page reads as 1, and a page past the last reachable one (whose offset is within 10,000 rows) reads as the last reachable page.","in":"query","name":"page","required":false,"schema":{"default":1,"minimum":1,"type":"integer"}},{"description":"Set false to return counts and metadata with an empty organizations array. Cannot be combined with format=csv.","in":"query","name":"includeRows","required":false,"schema":{"default":true,"type":"boolean"}},{"description":"Set false to omit resolved_cover and skip its read, for a reader that shows the row's own photo (image_urls). Defaults to true; must be exactly true or false, given once.","in":"query","name":"includeCover","required":false,"schema":{"default":true,"type":"boolean"}},{"description":"Page size from 1 to 100 for JSON. In the family projection, up to 500 with fields=pins, and limit=0 returns total without rows. Read as the legacy explorer read it: an absent, non-numeric or non-positive limit reads as the default, a fraction is floored (below 1 answers no rows), and a larger value reads as the maximum.","in":"query","name":"limit","required":false,"schema":{"default":24,"maximum":100,"minimum":1,"type":"integer"}},{"description":"Returns records whose stated whole completed age range in inclusive age_range_years bounds includes this age in years.","in":"query","name":"ageYears","required":false,"schema":{"example":10,"maximum":8333,"minimum":0,"type":"integer"}},{"description":"Requires a direct staff or ADMIN account; service delegation, machine keys, and previews are refused. Filters organizations by one shared camp collection; map routes include coherently owned descendants.","in":"query","name":"collectionId","required":false,"schema":{"format":"uuid","type":"string"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Origin for distanceMiles, radiusMiles and orderBy=distance: home (the household's searchArea.point; 409 when it is unset; 400 for the consumer site key) or latitude,longitude in decimal degrees.","in":"query","name":"near","required":false,"schema":{"type":"string","example":"47.61,-122.33"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Keeps rows whose own coordinates are within this many miles of near; rows without coordinates are excluded. Requires near.","in":"query","name":"radiusMiles","required":false,"schema":{"type":"integer","minimum":1,"maximum":50}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. A map's view: west,south,east,north in decimal degrees (west greater than east crosses the antimeridian; south below north). Keeps rows whose point (their own, else the one they inherit) is street-grade (coordinate_precision exact, building, parcel or street) and inside the view; a ZIP or state centre is never in view. Combines with every other filter, near, orderBy=distance and fields=pins; counts (limit=0) count the rows in view. 400 when it is not four finite numbers describing a nonempty view.","in":"query","name":"bbox","required":false,"schema":{"type":"string","example":"-122.75,45.45,-122.55,45.6"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. pins alone returns only {id, latitude, longitude, name} for rows with coordinates, with limit up to 500. Otherwise a comma-separated list of the costly list fields to add to each row: familiesBooked, priceTier, contentUpdatedAt (a detail always carries them; a list row carries one only when named here).","in":"query","name":"fields","required":false,"schema":{"type":"string","pattern":"^(pins|(familiesBooked|priceTier|contentUpdatedAt)(,(familiesBooked|priceTier|contentUpdatedAt))*)$","example":"familiesBooked"}},{"description":"Consumer site key only; any other credential gets 400 (a signed-in family reads in its household time zone). The visitor's IANA time zone (UTC or an Area/Location tz database name), which sets the day availability, nextSessions and date filters read from. Without it, signed-out reads use the launch metro's zone (America/Los_Angeles). An unknown or process-dependent name (Local) is 400.","in":"query","name":"tz","required":false,"schema":{"type":"string","example":"America/Los_Angeles"}},{"name":"filter","in":"query","required":false,"description":"One URL-encoded JSON predicate tree, sent once. Combines with all legacy filters using AND before rows, totals and facet counts are selected. Read /api/canonical/schema tables[*].filterCapabilities for visible fields, operators, relations and limits. Invalid/unknown/over-complex filters return 400; unavailable fields return 403, including in nonmatching branches. Do not broaden a failed request by dropping predicates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalFilter"}}}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"name":"includeResolution","in":"query","required":false,"description":"Include field_resolution on each returned entity row: own, inherited, or unset for each projected field. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeSources","in":"query","required":false,"description":"Include field_sources on each returned entity row, mapping fields to the canonical entity supplying their effective value or configuration unit. Independent of includeResolution; defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeOwnValues","in":"query","required":false,"description":"Include own_values on each returned entity row, retaining null local values. Requires editor authorization; otherwise returns 403. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"example":{"data":{"limit":1,"order":"desc","orderBy":"updated_at","organizations":[{"city":"Seattle","id":"00000000-0000-0000-0000-000000000000","name":"Camp Wildwood","region":"WA","image_urls":["https://images.example.com/camp-wildwood/cover.jpg"],"logo_url":"https://images.example.com/camp-wildwood/logo.png"}],"page":1,"pageCount":143,"tier":"paid","total":143},"success":true}}},"description":"A tier-masked page. total reflects every ordinary and score filter before pagination. Records omit scores; use the separate score endpoints when needed."},"400":{"content":{"application/json":{"example":{"data":null,"error":true,"message":"Query parameter \"minimumScore\" must be a whole number between 0 and 100."}}},"description":"A score bound is not a whole number from 0 through 100, minimumScore exceeds maximumScore, or q is invalid. Also returned for malformed, repeated, unsupported, over-complex, or incorrectly typed filter JSON."},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"500":{"$ref":"#/components/responses/ServerError"},"409":{"description":"near=home when the family's household has neither a searchArea.point nor a known searchArea.postalCode (a co-parent's or caregiver's home is the family owner's)."},"429":{"description":"Request allowance exceeded, with Retry-After. A data-API key's hourly read allotment answers retryAfterMs, readsAllotment and windowMs. A signed-in family session or the consumer site key answers its per-minute app limit with retryAfterMs and windowMs only (the FamilyRequestLimit shape)."},"403":{"description":"A filter names a field or relation outside the current plan or preview disclosure."}},"summary":"Query organizations","tags":["Organization catalog"],"x-auth":"Account or named integration API key on Starter or above. The response fields are masked to the owner’s current plan. A consumer's own signed-in account reads at any tier, from sign-up on, with the family fields added to its tier's (below Starter, the family fields alone). The parent site's catalog-only consumer site key (SCHOOLS_OUT_CATALOG_API_KEY) also reads it for signed-out visitors, as a Free family with no household: the family fields only, near as latitude,longitude (near=home is 400), and the day in the visitor's tz (default: the launch metro's zone).","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/organizations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"minimumScore=81\" \\\n  --data-urlencode \"limit=1\"","title":"Count organizations scoring above 80"},{"code":"curl --get \"https://apis.schoolsoutapp.com/api/organizations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"region=WA\" \\\n  --data-urlencode \"minimumScore=80\" \\\n  --data-urlencode \"orderBy=score\" \\\n  --data-urlencode \"order=desc\" \\\n  --data-urlencode \"limit=25\"","title":"List the highest-scoring organizations in Washington"}]}},"/api/organizations/facets":{"get":{"description":"Counts under the canonical list filters and current field access. A single-field request with snapshot=true and no search or filters reads durable incrementally maintained counts after relevant background work settles. While committed edits, deletion, blacklisting, or calendar expiry work is pending, the request falls back to live counts; failed refreshes leave that fallback active. Filtered requests and the default stay live. collectionId is staff-only; collections contain organizations and do not infer activity or session category labels. Organization category facets read each organization’s own categories; they do not aggregate activity labels. The same structured filter JSON and legacy list filters narrow the population using AND before aggregation. Structured filters always use live counts. q searches facet values; search carries the list text search. All filter branches enforce current field disclosure.","parameters":[{"description":"Facet field.","in":"query","name":"field","schema":{"enum":["city","region","category","updated_day"],"type":"string"}},{"description":"Use the stored unfiltered chart distribution. Must be exactly true or false.","in":"query","name":"snapshot","schema":{"default":false,"type":"boolean"}},{"in":"query","name":"limit","schema":{"default":30,"maximum":100,"minimum":1,"type":"integer"},"description":"With field, how many values to return: a whole number from 1 to 100, given once; any other value answers 400."},{"description":"Filter records before aggregation; uses live counts.","in":"query","name":"search","schema":{"type":"string"}},{"description":"Search facet values; uses live counts.","in":"query","name":"q","schema":{"type":"string"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): words that must each match whole. Every word of every value (values split at whitespace) must be in the record's family search fields (the search fields a family sees, as a family sees them, so hidden text never matches) as a whole word or a word of the same English stem, case-insensitively (art matches Arts but not party; camps matches camp); a value's words may match in different fields. Stop words (the, for) are ignored, so a value of stop words alone keeps every row. ANDed with contains, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters; at most 10 words in all. For partial words, send contains.","in":"query","name":"text","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): text that each must contain. Each value matches when any visible search field contains it anywhere, case-insensitively, as one q value does (art matches Arts and party), as the reader sees the field, so hidden text never matches. ANDed with each other, with text, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters. Very common fragments of a few letters are the slowest searches.","in":"query","name":"contains","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Requires a direct staff or ADMIN account; service delegation, machine keys, and previews are refused. Filters organizations by one shared camp collection; map routes include coherently owned descendants.","in":"query","name":"collectionId","required":false,"schema":{"format":"uuid","type":"string"}},{"description":"Returns organizations whose stated whole completed age range in inclusive age_range_years bounds includes this age in years.","in":"query","name":"ageYears","required":false,"schema":{"example":10,"maximum":8333,"minimum":0,"type":"integer"}},{"description":"Legacy compatibility filter for month-based ages. New clients should use whole-year ageYears; when a whole-year range is sent through a legacy month-shaped adapter, inclusive bounds expand as [yearMin*12,yearMax*12+11].","in":"query","name":"ageMonths","required":false,"schema":{"example":120,"maximum":100000,"minimum":0,"type":"integer"}},{"description":"Consumer site key only; any other credential gets 400 (a signed-in family reads in its household time zone). The visitor's IANA time zone (UTC or an Area/Location tz database name), which sets the day availability, nextSessions and date filters read from. Without it, signed-out reads use the launch metro's zone (America/Los_Angeles). An unknown or process-dependent name (Local) is 400.","in":"query","name":"tz","required":false,"schema":{"type":"string","example":"America/Los_Angeles"}},{"description":"Case-insensitive exact city name or its lowercase hyphenated slug.","in":"query","name":"city","required":false,"schema":{"example":"seattle","type":"string"}},{"description":"Case-insensitive exact region, such as a state abbreviation.","in":"query","name":"region","required":false,"schema":{"example":"WA","type":"string"}},{"description":"Exact category recorded on the organization itself; categories are independent across organizations, activities, and sessions.","in":"query","name":"category","required":false,"schema":{"example":"STEM","type":"string"}},{"description":"Returns organizations whose stated grade range includes this grade. Use -1 for pre-K and 0 for kindergarten.","in":"query","name":"grade","required":false,"schema":{"example":5,"maximum":12,"minimum":-1,"type":"integer"}},{"description":"Inclusive minimum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response.","in":"query","name":"minimumScore","required":false,"schema":{"example":81,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Inclusive maximum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response. Must not be below minimumScore.","in":"query","name":"maximumScore","required":false,"schema":{"example":100,"maximum":100,"minimum":0,"type":"integer"}},{"name":"filter","in":"query","required":false,"description":"One URL-encoded JSON predicate tree, sent once. Combines with all legacy filters using AND before rows, totals and facet counts are selected. Read /api/canonical/schema tables[*].filterCapabilities for visible fields, operators, relations and limits. Invalid/unknown/over-complex filters return 400; unavailable fields return 403, including in nonmatching branches. Do not broaden a failed request by dropping predicates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalFilter"}}}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Existing success envelope containing facets, tier, resultLimit, and sourceClass. A single field returns values with value, label, and rows. Without field (all facets), each free-text facet (city, region, category, skillLevel, format, seasonality) lists at most facetValueLimit values (default 100, a tunable policy), its most common by count; a value the request filters that field on is always listed, first. facetValueLimit is in the envelope. Read one facet with field=, limit and q for the rest."},"400":{"description":"Invalid field or snapshot option. Also returned for malformed, repeated, unknown, over-complex or incorrectly typed structured filter JSON."},"401":{"description":"Authentication required."},"402":{"description":"Starter or higher required."},"429":{"description":"Request allowance exceeded, with Retry-After. A data-API key's hourly read allotment answers retryAfterMs, readsAllotment and windowMs. A signed-in family session or the consumer site key answers its per-minute app limit with retryAfterMs and windowMs only (the FamilyRequestLimit shape)."},"403":{"description":"A structured filter names a field or relationship outside current tier or preview disclosure."}},"summary":"Read organizations facet or chart counts","tags":["Canonical"],"x-auth":"Account or named integration API key on Starter or above. Facet fields follow the owner’s current plan. A consumer's own signed-in account reads the family facets at any tier, from sign-up on. The parent site's catalog-only consumer site key (SCHOOLS_OUT_CATALOG_API_KEY) also reads it for signed-out visitors, as a Free family with no household: the family fields only, near as latitude,longitude (near=home is 400), and the day in the visitor's tz (default: the launch metro's zone).","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/organizations/facets\" -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" --data-urlencode \"field=region\" --data-urlencode \"snapshot=true\"","title":"Read organizations chart counts"}]}},"/api/organizations/{id}":{"get":{"description":"Returns one tier-masked canonical organization and its Activity summaries without loading or calculating scores. Deprecated includeScore=false is accepted as a no-op; includeScore=true returns 400 directing callers to /api/organizations/{id}/score. Repeated or malformed includeScore values return 400. Normal JSON records omit bookkeeping and ingestion history, null and blank record fields, and unknown availability, registration-status, and coordinate-precision values. Zero, false, empty arrays, masked values, positional range bounds, and structured schedule states retain their meanings. Related entity summaries follow the same projection; CSV retains its existing contract. Nonblank configuration_id is retained as record identity metadata for feedback targeting and omitted from display column metadata. Moved fields are external_ids, created_at, updated_at, source_created_at, field_provenance, source_records, merge_jobs, and relationship_jobs. Read permitted bookkeeping and provenance at /api/organizations/{id}/provenance. Age ranges use age_range_years; normal JSON records and column metadata omit the redundant age_range_months alias. CSV and the legacy ageMonths filter retain month compatibility. Records and column metadata omit score. Read /api/organizations/{id}/score for live detail or /api/organizations/scores?ids=UUID,UUID&cached=true for persisted summaries. Normal detail responses include data.media, the first gallery page of up to 100 images, with entity, items, coverId, logoId, total, limit, and offset. Images include displayUrl, label, kind, width, and height; source attribution is omitted even for ADMIN. Previews omit media. Optional includeMedia=false skips the gallery; omitted or true includes it. The parameter accepts one exact true or false value, with malformed or repeated values returning 400. Use the existing /media endpoint for additional pages or authorized source attribution. On /media, includeProvenance=false skips source attribution; omitted or true retains existing authority-gated sources. Use limit=1 at the selected image offset when requesting only its source history. A consumer's own signed-in account reads at any tier, from sign-up on with the family fields added: data.organization carries its tier's fields (none below Starter) plus the FamilyCatalogRecord fields; data.organization.sessions lists the first sessionsLimit live sessions by start date, with availability, and data.organization.activities the first activitiesLimit activities by name, each with its total (sessionsTotal, activitiesTotal); data.projection is family; lockedColumns lists what the tier locks and the family fields do not show; moderation is omitted below Starter. Ordinary permitted JSON records include resolved_cover with the ultimate source entity, inheritance flag, and structured stock attribution. Owned gallery and image compatibility fields retain their existing meaning.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Deprecated no-op accepting false only. Omit this parameter; scores are read from /api/organizations/{id}/score. true, malformed, and repeated values return 400.","in":"query","name":"includeScore","schema":{"type":"boolean","enum":[false]},"deprecated":true},{"name":"includeMedia","in":"query","required":false,"description":"One exact true or false value. Omitted or true bundles the first gallery page; false skips media for follow-up diagnostic reads. Previews always omit media.","schema":{"type":"boolean","default":true}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. How many sessions (by start date) the detail lists, from 1 to 500; default 100. sessionsTotal counts them all.","in":"query","name":"sessionsLimit","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. How many activities (by name) the detail lists, from 1 to 500; default 100. activitiesTotal counts them all.","in":"query","name":"activitiesLimit","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"description":"Consumer site key only; any other credential gets 400 (a signed-in family reads in its household time zone). The visitor's IANA time zone (UTC or an Area/Location tz database name), which sets the day availability, nextSessions and date filters read from. Without it, signed-out reads use the launch metro's zone (America/Los_Angeles). An unknown or process-dependent name (Local) is 400.","in":"query","name":"tz","required":false,"schema":{"type":"string","example":"America/Los_Angeles"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"name":"includeResolution","in":"query","required":false,"description":"Include field_resolution on each returned entity row: own, inherited, or unset for each projected field. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeSources","in":"query","required":false,"description":"Include field_sources on each returned entity row, mapping fields to the canonical entity supplying their effective value or configuration unit. Independent of includeResolution; defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeOwnValues","in":"query","required":false,"description":"Include own_values on each returned entity row, retaining null local values. Requires editor authorization; otherwise returns 403. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"organization":{"anyOf":[{"$ref":"#/components/schemas/CanonicalDetailRow"},{"$ref":"#/components/schemas/FamilyCatalogRecord"}]},"media":{"$ref":"#/components/schemas/CanonicalMediaPage"}},"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Canonical organization detail"},"404":{"$ref":"#/components/responses/NotFound"},"400":{"description":"includeScore=true, malformed/repeated includeScore, or invalid detail parameters. Malformed or repeated includeMedia also returns 400."},"429":{"description":"Request allowance exceeded, with Retry-After. A data-API key's hourly read allotment answers retryAfterMs, readsAllotment and windowMs. A signed-in family session or the consumer site key answers its per-minute app limit with retryAfterMs and windowMs only (the FamilyRequestLimit shape)."},"403":{"description":"showRemoved=true requires a current staff or ADMIN operator; preview and non-operator viewers cannot opt in."}},"summary":"Read one canonical organization","tags":["Organization catalog"],"x-auth":"Account cookie or header, or permitted machine key, from Starter up. Active organization members receive their existing organization detail access. The parent site's catalog-only consumer site key (SCHOOLS_OUT_CATALOG_API_KEY) also reads it for signed-out visitors, as a Free family with no household: the family fields only, near as latitude,longitude (near=home is 400), and the day in the visitor's tz (default: the launch metro's zone).","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/organizations/$ORGANIZATION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Read one organization"}]}},"/api/organizations/{id}/brand/assets/{assetId}":{"get":{"summary":"Read an uploaded organization asset","description":"Returns the uploaded asset bytes with their recorded content type. A consumer’s own signed-in account below the brand tier gate and the consumer site credential may read active catalog image uploads belonging to a live, non-blacklisted organization. Documents, inactive or unpublished media, foreign uploads, and removed or deleted records are unavailable through this family access. Existing brand access for paid accounts, organization members, and operators remains unchanged. Catalog image_urls, logo_url, and resolved_cover may reference this route. Responses are privately cached; the caller’s credential must accompany the request.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"assetId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"download","description":"Send 1 for attachment disposition; otherwise the asset is served inline.","schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"The authorized uploaded asset.","content":{"image/*":{"schema":{"type":"string","format":"binary"}},"application/pdf":{"schema":{"type":"string","format":"binary"}}},"headers":{"Cache-Control":{"schema":{"type":"string"}},"ETag":{"schema":{"type":"string"}},"Content-Disposition":{"schema":{"type":"string"}}}},"400":{"description":"An organization or asset identifier is invalid."},"401":{"description":"Authentication required."},"402":{"description":"The caller has neither brand access nor family catalog image access."},"403":{"description":"The credential or request origin is not permitted."},"404":{"description":"The asset is unavailable within the authorized organization and image scope."}},"tags":["Organization catalog"],"x-auth":"An account or named integration key with existing brand read access; a consumer’s own session-backed account or the consumer site key may read active catalog images without a paid plan.","x-curl-examples":[{"title":"Read a catalog image upload","code":"curl \"https://apis.schoolsoutapp.com/api/organizations/$ORGANIZATION_ID/brand/assets/$ASSET_ID\" -H \"x-api-key: $SCHOOLS_OUT_CATALOG_API_KEY\" --output camp-image"}]}},"/api/organizations/{id}/media":{"get":{"description":"A consumer's own signed-in account may read media at any tier. The consumer site key may not; its detail reads take includeMedia=true. Returns this record’s owned paginated gallery and selections plus resolvedCover for display. Inherited covers retain their owning record and do not change gallery membership or counts. Stock covers include public provider and photographer credits. Remote URLs and R2 URLs are served directly; uploaded assets retain protected URLs, except the selected logo (logoId), which once rendered is its public, cross-origin logo rendition URL (GET /api/media/logos/{file}). Source evidence is available only to non-preview ADMIN accounts. Pagination uses the existing catalog bounds. Authorized showRemoved=true retains this gallery and its selections for staff inspection. Protected uploaded URLs under /api/organizations/{id}/brand/assets/{assetId} remain readable by current operators after removal under the existing brand-read access checks; ordinary viewers cannot use those binary reads to inspect removed records.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Defaults to 30 when absent or invalid; values over 100 cap at 100.","in":"query","name":"limit","schema":{"default":30,"maximum":100,"minimum":1,"type":"integer"}},{"description":"Defaults to 0 when absent or invalid; values over 1000000 cap at 1000000.","in":"query","name":"offset","schema":{"default":0,"maximum":1000000,"minimum":0,"type":"integer"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/CanonicalMediaPage"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Paginated media and selected IDs"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Starter access is required."},"403":{"description":"Previews cannot read media."},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Read organization media","tags":["Organization catalog"],"x-auth":"Starter or above, or an active member of the requested organization; previews are denied. Resolved covers follow the same media access gates.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/organizations/$ORGANIZATION_ID/media\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"limit=30\" \\\n  --data-urlencode \"offset=0\"","title":"Read the first page of an organization gallery"}]}},"/api/organizations/{id}/update-form":{"post":{"description":"Saves attributed, reversible organization edits. Omitted and blank/null fields are ignored. clearFields explicitly clears editable non-required fields; required fields such as name cannot be cleared. A clear or replacement of a populated, different value requires reason; filling a missing value or confirming the same value does not. sourceURL is an optional HTTP(S) citation for submitted field values. Human edits outrank automated contributions, including human-approved Overseer proposals; source freshness applies within one authority. Adds supporting links from organization.relevant_urls and removes explicitly selected supporting links from removeRelevantUrls. Both use HTTP(S) form normalization. Unspecified and concurrently added URLs remain; removal wins over a simultaneous addition. Removing the last link is allowed. Duplicate additions and absent removals record no new ownership. The official canonical_url is separate. Changes retain attributed, reversible contribution history; later removals remain authoritative during undo. Public update-token forms reject removeRelevantUrls and clearFields for relevant_urls; supporting links remain add-only there.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"assets":{"items":{"type":"object"},"type":"array"},"brand":{"type":"object"},"clearFields":{"description":"Names of editable fields to clear. Cannot include required fields or a field also present in organization. For relevant_urls, this clears all supporting links; use removeRelevantUrls to remove selected links.","items":{"type":"string"},"type":"array"},"organization":{"description":"Omitted and blank/null fields are ignored. relevant_urls is an additive HTTP(S) array or newline-separated list. Page fields take one shape each and are refused with 400, never cut: summary (54 characters or fewer), highlights (at most 3 {icon, title}), faqs (at most 10 {question, answer}; updated_at is set by the server), offers ({key, offered, detail?} on the keys in the form's entryOptions.key), staff_ratio ({adults, kids} whole numbers), registration_mode (instant or application), and the staff-only credentials ({kind, label, issuer?, checked_at, source_url?}). Structured page fields accept JSON values or JSON text.","type":"object"},"reason":{"description":"Human rationale required for every clear or replacement of a populated, different value; optional for fills and same-value confirmations.","maxLength":2000,"type":"string"},"removeRelevantUrls":{"description":"Explicit supporting links to remove using form URL normalization. Unspecified URLs remain; removal wins over simultaneous addition. A nonempty list permits removal-only saves.","items":{"format":"uri","type":"string"},"type":"array"},"sourceURL":{"description":"Optional absolute HTTP(S) citation without credentials for submitted field values.","format":"uri","maxLength":2000,"type":"string"},"submitter":{"type":"object"}},"type":"object"}}},"required":true},"responses":{"200":{"description":"Saved organization, changedFields, submittedFields, batchId and brand. Clear operations and conflicting replacements require a human reason; no effective URL change creates no URL contribution."},"400":{"description":"Invalid edit or URL list."},"401":{"description":"Authentication required."},"403":{"description":"No camp edit permission, or a member sent or cleared a staff-only field (flags, credentials)."},"404":{"description":"Camp not found."}},"security":[{"AccountCredential":[]}],"summary":"Save attributed camp field edits","tags":["Internal organization editing"],"x-auth":"Current ADMIN/staff account or active organization member. Machine keys do not inherit membership; cookie POST requests require CSRF.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/organizations/$ORGANIZATION_ID/update-form\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"organization\":{\"relevant_urls\":[\"https://www.example.org/summer-camps\"]},\"removeRelevantUrls\":[\"https://www.example.org/old-camp-page\"]}'","title":"Add one supporting link and remove another"}],"x-internal":true}},"/api/overseer":{"get":{"description":"Reads or replaces the shared Overseer instructions and tool controls. Structured saves supply directives and optionally disabledTools with expectedRevision. Each edit atomically creates a revision, fences previous execution, and retains unfinished one-off work with its saved progress under the new revision; committed child jobs remain. Original request identity, including its submitted expectedRevision, remains unchanged. Empty or all-disabled instructions idle the worker unless a one-off message, requested correction, or system revalidation is pending. Global pause still prevents execution. Legacy directive text is supported: saving its unchanged projection preserves structured instructions, while changed text replaces them with one enabled item; omitted disabledTools preserves restrictions. Stale expectedRevision returns 409 and the current snapshot. executionActive reports a current unexpired worker lease, not worker health. Native thread IDs, checkpoints and execution tokens remain private. latestSlackDelivery is a safe durable observation, not a claim about current channel configuration. paused and pauseVersion expose a global control independent of instruction revision. Configuration saves never implicitly resume paused execution.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerSnapshot"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Current directive and safe progress projection."},"401":{"description":"Authentication required."},"403":{"description":"Current identity is not a user-backed staff or ADMIN operator, or the browser mutation lacks a trusted origin and session-bound CSRF token."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read Overseer instructions, tools and progress","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN identity. Account credentials and machine keys belonging to eligible users are accepted; a bare service credential is not. Intelligence uses its signed session cookie; cookie PUT requests require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read the directive and current progress"}],"x-internal":true},"put":{"description":"Reads or replaces the shared Overseer instructions and tool controls. Structured saves supply directives and optionally disabledTools with expectedRevision. Each edit atomically creates a revision, fences previous execution, and retains unfinished one-off work with its saved progress under the new revision; committed child jobs remain. Original request identity, including its submitted expectedRevision, remains unchanged. Empty or all-disabled instructions idle the worker unless a one-off message, requested correction, or system revalidation is pending. Global pause still prevents execution. Legacy directive text is supported: saving its unchanged projection preserves structured instructions, while changed text replaces them with one enabled item; omitted disabledTools preserves restrictions. Stale expectedRevision returns 409 and the current snapshot. executionActive reports a current unexpired worker lease, not worker health. Native thread IDs, checkpoints and execution tokens remain private. latestSlackDelivery is a safe durable observation, not a claim about current channel configuration. paused and pauseVersion expose a global control independent of instruction revision. Configuration saves never implicitly resume paused execution.","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"directive":{"description":"At most 65536 UTF-8 bytes. Empty or whitespace-only text idles the runner.","type":"string"},"expectedRevision":{"description":"Revision returned by the last snapshot the caller reviewed.","minimum":0,"type":"integer"},"directives":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/OverseerDirective"}},"disabledTools":{"type":"array","uniqueItems":true,"items":{"type":"string"},"description":"Disabled names from availableTools. Mandatory history, review reconciliation and lifecycle tools cannot be disabled."}},"required":["expectedRevision"],"type":"object","oneOf":[{"required":["directive"],"not":{"required":["directives"]}},{"required":["directives"],"not":{"required":["directive"]}}]}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerSnapshot"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Saved directive at its new revision."},"400":{"description":"Invalid directive or expected revision, or unsupported input field."},"401":{"description":"Authentication required."},"403":{"description":"Current identity is not a user-backed staff or ADMIN operator, or the browser mutation lacks a trusted origin and session-bound CSRF token."},"409":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerSnapshot"},"error":{"const":true},"message":{"type":"string"},"success":{"const":false}},"required":["data","success","message"],"type":"object"}}},"description":"The directive changed. Preserve the local draft and review the current snapshot before retrying."}},"security":[{"ApiKeyAuth":[]}],"summary":"Save Overseer instructions and tool controls","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN identity. Account credentials and machine keys belonging to eligible users are accepted; a bare service credential is not. Intelligence uses its signed session cookie; cookie PUT requests require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"title":"Save enabled and disabled instructions","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/overseer\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"directives\":[{\"id\":\"washington\",\"text\":\"Verify Washington camp schedules.\",\"enabled\":true}],\"disabledTools\":[\"start_crawler\"],\"expectedRevision\":12}'"}],"x-internal":true}},"/api/overseer/clear-memory":{"post":{"description":"Clears the current execution memory while retaining the exact saved directive and report cadence. It preserves the directive revision, revokes the current run, and emits a memory_cleared event when a saved revision exists. The next execution starts with a fresh native thread and no prior checkpoint or narrative. Historical jobs, action receipts, audit events, and minimal action references remain for provenance and to avoid duplicate actions. A blank or absent directive is allowed; requested corrections and system revalidation remain actionable. Review history and suppression are preserved. A stale expectedRevision returns 409 with the current public snapshot. Thread IDs, checkpoints, and execution authority remain private. Structured instruction enablement and tool restrictions remain unchanged. Unfinished one-off requests retain their saved progress and immutable submission identity; the reset releases their claims and advances their work versions for the next execution. The global pause state and its version are preserved.","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"expectedRevision":{"description":"Revision returned by the snapshot the operator reviewed.","minimum":0,"type":"integer"}},"required":["expectedRevision"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerSnapshot"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Existing directive revision with cleared conversation memory."},"400":{"description":"Missing, invalid, or unsupported request input."},"401":{"description":"Authentication required."},"403":{"description":"Current identity is not a user-backed staff or ADMIN operator, or the browser mutation lacks a trusted origin and session-bound CSRF token."},"409":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerSnapshot"},"error":{"const":true},"message":{"type":"string"},"success":{"const":false}},"required":["data","success","message"],"type":"object"}}},"description":"The directive changed. Review the current snapshot before clearing memory again."}},"security":[{"ApiKeyAuth":[]}],"summary":"Clear Overseer conversation memory","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN identity. Account credentials and machine keys belonging to eligible users are accepted; a bare service credential is not. Intelligence uses its signed session cookie; cookie POST requests require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/clear-memory\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"expectedRevision\":12}'","title":"Clear memory for the directive reviewed at revision 12"}],"x-internal":true}},"/api/overseer/events":{"get":{"description":"Reads bounded Overseer history. Default order=asc uses the exclusive afterId cursor and returns nextAfterId. order=desc uses exclusive beforeId and returns nextBeforeId; omit the cursor for the newest page. Both return events in the requested order and hasMore, preserve an empty page cursor, accept optional revision (0 means all), and limit 1 to 200. Wrong-direction, mixed, duplicate, unsupported and invalid query parameters return 400. Only directive_saved events include a top-level directive loaded from that durable revision, including empty text for idle; other events omit it. Organization links in details.links include a display label resolved from the current camp name, including historical events. Unnamed or unavailable camps receive an identifying fallback containing the preserved camp ID. Labels do not change stored audit records or action receipts. Completed assistant messages use kind=agent_output with details.text, source (coordinator or subagent), phase (commentary or final_answer), optional ordinal agentLabel, and truncated. Text preserves line breaks after credential redaction and byte bounds. Coordinator final control documents, reasoning, tool payloads and native identities are excluded. These observations do not replace verified progress or directive state; existing history pagination applies.","parameters":[{"description":"Default asc. Desc reads newest events first.","in":"query","name":"order","schema":{"enum":["asc","desc"],"type":"string"}},{"description":"Exclusive ascending cursor. Rejected when order=desc.","in":"query","name":"afterId","schema":{"minimum":0,"type":"integer"}},{"description":"Exclusive descending cursor. Only accepted with order=desc; omit or use 0 for the newest page.","in":"query","name":"beforeId","schema":{"minimum":0,"type":"integer"}},{"description":"Optional exact revision; omitted or 0 includes all revisions.","in":"query","name":"revision","schema":{"minimum":0,"type":"integer"}},{"description":"Maximum events returned, default 100.","in":"query","name":"limit","schema":{"maximum":200,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"oneOf":[{"not":{"required":["nextBeforeId"]},"required":["nextAfterId"]},{"not":{"required":["nextAfterId"]},"required":["nextBeforeId"]}],"properties":{"events":{"items":{"$ref":"#/components/schemas/OverseerEvent"},"type":"array"},"hasMore":{"type":"boolean"},"nextAfterId":{"description":"Present for default/asc order; use as the next afterId.","minimum":0,"type":"integer"},"nextBeforeId":{"description":"Present only for order=desc; use as the next beforeId.","minimum":0,"type":"integer"}},"required":["events","hasMore"],"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Events in ascending ID order and their next cursor."},"400":{"description":"Unsupported, duplicate, malformed or out-of-range query parameter."},"401":{"description":"Authentication required."},"403":{"description":"Current identity is not a user-backed staff or ADMIN operator, or the browser mutation lacks a trusted origin and session-bound CSRF token."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read Overseer audit events","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN identity. Account credentials and machine keys belonging to eligible users are accepted; a bare service credential is not. Intelligence uses its signed session cookie; cookie PUT requests require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/events\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"order=desc\" \\\n  --data-urlencode \"limit=50\"","title":"Read the newest audit events"}],"x-internal":true}},"/api/overseer/evidence/{id}":{"get":{"description":"Returns original captured pixels while retained. The global newest 10000 Overseer images are kept; metadata survives removal. Removed images return HTTP 410 and are never recreated by replay.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"image/png":{"schema":{"format":"binary","type":"string"}}},"description":"Captured source PNG.","headers":{"Cache-Control":{"schema":{"type":"string"}},"ETag":{"schema":{"type":"string"}}}},"400":{"description":"Invalid or unsupported input."},"401":{"description":"Authentication required."},"403":{"description":"Current operator access and, for cookie decisions, trusted origin and session-bound CSRF are required."},"404":{"description":"Review or source capture not found."},"410":{"description":"Image bytes are no longer retained; capture metadata remains available."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read immutable research source pixels","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN operator; account credentials or eligible machine keys, or the signed Intelligence cookie. Cookie decisions require a trusted origin and session-bound CSRF token. Bare service credentials and preview viewers are rejected.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/evidence/$EVIDENCE_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --output \"overseer-evidence.png\"","title":"Download one source capture"}],"x-internal":true}},"/api/overseer/jobs":{"get":{"description":"Reads current central scraper queue status for explicit durable Overseer action links across revisions. Crawler links remain in actions and events; current crawler state is read directly from the local worker in the Crawler tab. Jobs are deduplicated by kind and ID and attributed to their first linked action revision. Newest first uses an opaque exclusive before cursor and returns jobs, nextBefore and hasMore; limit is 1 to 200, and optional revision selects the originating revision. Existing cursors containing crawler job identities remain valid continuation boundaries. Later references do not move a job or change its origin. label is the scraper website. campId is nonnull only for one explicit organization link on the originating action that resolves to a live camp; no website/name match or inferred followup establishes a relationship. Missing jobs and other link kinds are omitted. Raw action inputs, results, evidence and execution authority are never returned.","parameters":[{"description":"Opaque exclusive cursor from nextBefore. Omit for newest linked jobs.","in":"query","name":"before","schema":{"type":"string"}},{"description":"Optional originating revision; omitted or 0 includes all revisions.","in":"query","name":"revision","schema":{"minimum":0,"type":"integer"}},{"description":"Maximum jobs returned, default 100.","in":"query","name":"limit","schema":{"maximum":200,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"hasMore":{"type":"boolean"},"jobs":{"items":{"$ref":"#/components/schemas/OverseerLinkedJob"},"type":"array"},"nextBefore":{"type":["string","null"]}},"required":["jobs","nextBefore","hasMore"],"type":"object"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Linked jobs, current queue status and stable backward cursor."},"400":{"description":"Malformed cursor, unsupported or duplicate query, or out-of-range limit/revision."},"401":{"description":"Authentication required."},"403":{"description":"Current user-backed staff/ADMIN operator required; preview denied."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read linked Overseer jobs","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN identity. Account credentials and machine keys belonging to eligible users are accepted; a bare service credential is not. Intelligence uses its signed session cookie; cookie PUT requests require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/jobs\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"limit=50\"","title":"Read the newest linked jobs"}],"x-internal":true}},"/api/overseer/review-groups":{"get":{"description":"Reads retained update versions across all instruction revisions. Application and optional review status are independent. Filters and stable cursor pagination select history without granting mutation authority. Evidence images are served separately through the authenticated evidence endpoint. Removal proposals use operation=remove with pending_approval until a direct staff account approves or denies them; neither pending nor denied proposals are automatic correction work.","parameters":[{"in":"query","name":"purpose","description":"Separate removal proposals (remove) from general updates (edit). Omission includes both. Removal pages prioritize current pending approvals; combining remove with pending_approval selects only current actionable versions. Cursors and counts stay within the selected purpose.","schema":{"type":"string","enum":["edit","remove"]}},{"in":"query","name":"status","schema":{"type":"string","enum":["pending_approval","denied","applied","not_applied","revalidation_requested","reverting","reverted","revert_failed","all"]}},{"in":"query","name":"auditStatus","schema":{"type":"string","enum":["unreviewed","reviewed","changes_requested","all"]}},{"in":"query","name":"organizationId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"q","schema":{"type":"string","maxLength":200}},{"in":"query","name":"afterId","schema":{"type":"string","maxLength":512}},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":50,"default":25}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerReviewGroupsPage"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Paged update history."},"400":{"description":"Invalid or unsupported input."},"401":{"description":"Authentication required."},"403":{"description":"Current operator access is required."}},"security":[{"ApiKeyAuth":[]}],"summary":"Group update history by business","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN operator; account credentials or eligible machine keys, or the signed Intelligence cookie. Bare service credentials and preview viewers are rejected.","x-curl-examples":[{"title":"Read unreviewed updates","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/review-groups\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" --data-urlencode \"auditStatus=unreviewed\" --data-urlencode \"limit=25\""}],"x-internal":true}},"/api/overseer/reviews":{"get":{"description":"Reads every saved update version across all instruction revisions. Application and optional review status are independent. Paired entityKind and entityId select one exact record, excluding child records. retainedOnly=true selects versions applied at least once that reference a screenshot whose image remains retained, including later reverted versions. Partially expired versions preserve all evidence references and unavailable capture metadata. With a purpose, record, or retention scope, counts cover every matching version before pagination; other requests retain global history counts. Cursors bind all filters. Evidence images are served separately through the authenticated evidence endpoint. Removal proposals use operation=remove with pending_approval until a direct staff account approves or denies them; neither pending nor denied proposals are automatic correction work.","parameters":[{"in":"query","name":"purpose","description":"Separate removal proposals (remove) from general updates (edit). Omission includes both. Removal pages prioritize current pending approvals; combining remove with pending_approval selects only current actionable versions. Cursors and counts stay within the selected purpose.","schema":{"type":"string","enum":["edit","remove"]}},{"in":"query","name":"status","schema":{"type":"string","enum":["pending_approval","denied","applied","not_applied","revalidation_requested","reverting","reverted","revert_failed","all"]}},{"in":"query","name":"auditStatus","schema":{"type":"string","enum":["unreviewed","reviewed","changes_requested","all"]}},{"in":"query","name":"organizationId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"entityKind","description":"Exact record kind. Must be supplied together with entityId; excludes child records.","schema":{"type":"string","enum":["organization","activity","session"]}},{"in":"query","name":"entityId","description":"Exact canonical record UUID. Must be supplied together with entityKind.","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"retainedOnly","description":"Only versions with appliedAt and at least one referenced evidence capture whose screenshot image remains retained. Later reversal does not remove a version; partially expired evidence remains visible.","schema":{"type":"boolean","default":false}},{"in":"query","name":"q","schema":{"type":"string","maxLength":200}},{"in":"query","name":"afterId","schema":{"type":"string","maxLength":512}},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":50,"default":25}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerReviewPage"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Paged update history."},"400":{"description":"Invalid or unsupported input."},"401":{"description":"Authentication required."},"403":{"description":"Current operator access and, for cookie decisions, trusted origin and session-bound CSRF are required."},"404":{"description":"Review or source capture not found."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read update history","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN operator; account credentials or eligible machine keys, or the signed Intelligence cookie. Cookie decisions require a trusted origin and session-bound CSRF token. Bare service credentials and preview viewers are rejected.","x-curl-examples":[{"title":"Read unreviewed updates","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/reviews\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" --data-urlencode \"auditStatus=unreviewed\" --data-urlencode \"limit=25\""}],"x-internal":true}},"/api/overseer/reviews/{id}":{"get":{"description":"Returns one immutable update version, actual application receipt, baseline and applied values, current canonical context, source evidence metadata, and optional review feedback. Historical versions remain independently addressable after later updates. Removal detail retains the saved subtree, comparisons, and optional server-captured survivor baseline separately from current canonical values.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"version","schema":{"type":"integer","minimum":1},"description":"Read this exact retained update version. Omitted selects the latest."}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerReviewDetail"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Review and full-record comparison."},"400":{"description":"Invalid or unsupported input."},"401":{"description":"Authentication required."},"403":{"description":"Current operator access and, for cookie decisions, trusted origin and session-bound CSRF are required."},"404":{"description":"Review or source capture not found."}},"security":[{"ApiKeyAuth":[]}],"summary":"Inspect an update version and source evidence","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN operator; account credentials or eligible machine keys, or the signed Intelligence cookie. Cookie decisions require a trusted origin and session-bound CSRF token. Bare service credentials and preview viewers are rejected.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/reviews/$REVIEW_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Inspect one review"}],"x-internal":true}},"/api/overseer/reviews/{id}/decision":{"post":{"description":"Targets the exact expectedVersion. Mark reviewed changes audit state only. Request changes targets the latest version, leaves its contribution live, and wakes the existing Overseer worker for a fresh correction. Revert queues the version contribution batch through the existing reversal worker and suppresses further automatic updates for the record until request_changes explicitly reopens it. A queued reversal is reverting, not completed. Request IDs bind the exact normalized body; identical replay recovers its durable receipt or definitive rejection without executing again. Screenshot expiry does not block these actions. Current staff account authority and cookie CSRF remain required. For operation=remove, approve and deny accept only the current pending_approval version. Approve rechecks the retained target, affected-descendant snapshot, and optional survivor baseline under canonical write locks, removes that exact subtree, and records its reversible ingestion contribution. A survivor must remain active, same-kind, outside the subtree, and free of survivor chains or cycles. Approval links only the removed root through removal_survivor_id; it transfers no data, children, media, or history. Deny records a terminal denied result without altering canonical records. Other operations reject approve and deny. Removal request_changes creates explicit correction work; a revised proposal still requires fresh approval. Campaign-published versions reject request_changes; start an explicit targeted correction campaign. Existing retrospective review and revert actions remain available. The retry action explicitly resumes automatic updates only for the latest suppressed campaign version after its reversal is fully completed. It keeps the historical version reverted, marks it reviewed, and queues no legacy correction or research work; use a new targeted campaign for corrections.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverseerReviewDecisionInput"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerReview"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Recorded decision and resulting review."},"400":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerRejectedReviewDecision"},{"not":{"required":["decisionOutcome"]},"properties":{"data":{"type":"null"},"error":{"const":true},"message":{"type":"string"}},"type":"object"}]}}},"description":"Invalid input; a committed rejection of a well-formed decision additionally returns its requestId and decisionOutcome=rejected. Validation before persistence has no terminal marker."},"401":{"description":"Authentication required."},"403":{"description":"Current human account operator access is required; privileged machine keys and service-delegated accounts are denied. Cookie decisions also require a trusted origin and session-bound CSRF token."},"404":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerRejectedReviewDecision"},{"not":{"required":["decisionOutcome"]},"properties":{"data":{"type":"null"},"error":{"const":true},"message":{"type":"string"}},"type":"object"}]}}},"description":"Review or referenced record is missing. A committed rejection receipt includes requestId and decisionOutcome=rejected; an unmarked missing-record response does not resolve an earlier uncertain request."},"409":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerRejectedReviewDecision"},{"not":{"required":["decisionOutcome"]},"properties":{"data":{"type":"null"},"error":{"const":true},"message":{"type":"string"}},"type":"object"}]}}},"description":"Conflicting review or canonical values. Only a committed rejection receipt carries requestId and decisionOutcome=rejected; reuse of a request ID with different input returns an ordinary conflict without that marker."}},"security":[{"ApiKeyAuth":[]}],"summary":"Review an update or decide a pending removal","tags":["Internal Overseer"],"x-auth":"Current staff or ADMIN human account using an account credential or the signed Intelligence cookie. Cookie decisions require a trusted origin and session-bound CSRF token. Machine credentials, service delegation, bare service credentials and preview viewers cannot make review decisions.","x-curl-examples":[{"title":"Request a correction while keeping the current update live","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/reviews/$REVIEW_ID/decision\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"Content-Type: application/json\" --data '{\"expectedVersion\":3,\"requestId\":\"00000000-0000-4000-8000-000000000001\",\"action\":\"request_changes\",\"feedback\":\"Check the current schedule.\"}'"},{"title":"Approve the exact pending removal version","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/reviews/$REVIEW_ID/decision\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"Content-Type: application/json\" --data '{\"expectedVersion\":1,\"requestId\":\"00000000-0000-4000-8000-000000000002\",\"action\":\"approve\"}'"}],"x-internal":true}},"/api/sessions/facets":{"get":{"description":"Returns canonical session category, city, and registration-status facets under list filters and current field access. Categories are recorded on the session itself and are not inherited from its activity or organization. A single-field request with snapshot=true and no search or filters reads durable incrementally maintained counts after relevant background work settles. While committed edits, deletion, blacklisting, or calendar expiry work is pending, the request falls back to live counts; failed refreshes leave that fallback active. Filtered requests and the default stay live. A parent's own account with a family profile reads facets under the same family filters as the list (snapshot is ignored), with its tier's facets when its tier may read them plus the family facets: category, skill_level, format, seasonality, interest and age_group on activities, and category, city, registration_status, interest and start_week on sessions; interest values are feed interest ids with their labels, counted per offering. The same structured filter JSON and legacy list filters narrow the population using AND before aggregation. Structured filters always use live counts. q searches facet values; search carries the list text search. All filter branches enforce current field disclosure.","parameters":[{"description":"Facet field; required when snapshot=true.","in":"query","name":"field","schema":{"enum":["category","city","registration_status","start_week","updated_day"],"type":"string"}},{"description":"Use the stored unfiltered chart distribution. Must be exactly true or false.","in":"query","name":"snapshot","schema":{"default":false,"type":"boolean"}},{"in":"query","name":"limit","schema":{"default":30,"maximum":100,"minimum":1,"type":"integer"},"description":"With field, how many values to return: a whole number from 1 to 100, given once; any other value answers 400."},{"description":"Filter records before aggregation; uses live counts.","in":"query","name":"search","schema":{"type":"string"}},{"description":"Search facet values; uses live counts.","in":"query","name":"q","schema":{"type":"string"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): words that must each match whole. Every word of every value (values split at whitespace) must be in the record's family search fields (the search fields a family sees, as a family sees them, so hidden text never matches) as a whole word or a word of the same English stem, case-insensitively (art matches Arts but not party; camps matches camp); a value's words may match in different fields. Stop words (the, for) are ignored, so a value of stop words alone keeps every row. ANDed with contains, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters; at most 10 words in all. For partial words, send contains.","in":"query","name":"text","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): text that each must contain. Each value matches when any visible search field contains it anywhere, case-insensitively, as one q value does (art matches Arts and party), as the reader sees the field, so hidden text never matches. ANDed with each other, with text, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters. Very common fragments of a few letters are the slowest searches.","in":"query","name":"contains","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Sessions with an attended date on or after this date, or a date_range ending on or after it or stating no end (an ongoing session). On activities (family projection), an activity with such a session.","in":"query","name":"from","required":false,"schema":{"type":"string","format":"date"}},{"description":"Sessions with an attended date on or before this date, or a date_range starting on or before it. On activities (family projection), an activity with such a session.","in":"query","name":"to","required":false,"schema":{"type":"string","format":"date"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated YYYY-MM-DD dates, at most 31: a session attending any of them or whose date_range contains one (a date_range without an end runs on).","in":"query","name":"dates","required":false,"schema":{"type":"string","example":"2027-11-26,2027-11-27"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. true keeps sessions whose schedule covers a Saturday or Sunday: an attended weekend date (explicit dates or a weekday rule), or a date_range whose days include one. A range of a week or more, or one with a start and no end, always matches; a range without a start never does.","in":"query","name":"weekends","required":false,"schema":{"type":"boolean"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated morning, afternoon, evening, by the session's earliest start: before 12:00, before 17:00, then later.","in":"query","name":"timeOfDay","required":false,"schema":{"type":"string","example":"morning,afternoon"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated half_day, full_day, overnight. half_day and full_day compare the longest same-day span of non-overnight clocks; overnight is the session's explicit overnight attribute.","in":"query","name":"dayLength","required":false,"schema":{"type":"string","example":"full_day"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated before, after; every listed care must be offered (by the session's schedule or configuration, or for an activity by itself or any of its sessions). A configuration offers care when its availability is available (or a truthy value stored before normalization) or it gives the care clock.","in":"query","name":"care","required":false,"schema":{"type":"string","example":"before,after"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole US dollars. Keeps sessions whose sole stated price (session_prices) is at most priceMax: at priceBasis when it is given, else at whatever basis the price states, so a cap reads \"up to $N, whatever the period\". A price without a stated price_basis counts as session (the whole enrollment). A session with several quotes states no sole price and never matches; an activity matches through any of its live sessions. Other currencies are excluded.","in":"query","name":"priceMax","required":false,"schema":{"type":"integer","minimum":0}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. The basis priceMax compares at; requires priceMax. Without it, priceMax compares each price at the basis it states.","in":"query","name":"priceBasis","required":false,"schema":{"type":"string","enum":["hour","day","week","month","session"]}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. true keeps sessions whose availability is open or limited on the household-local today and that have not ended (a session without a stated end is ongoing).","in":"query","name":"openSpots","required":false,"schema":{"type":"boolean"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated whole years, each 0 to 17 (children of different ages): keeps records whose age_range_years contains any of them. With ageMin or ageMax, a record that fits any listed age or overlaps that range matches. A session that states no ages of its own is read with its activity's.","in":"query","name":"ages","required":false,"schema":{"type":"string","example":"4,11"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole years; keeps records whose age_range_years overlaps [ageMin, ageMax] (either end open), or fits one of ages when that is given too; a session that states no ages of its own is read with its activity's.","in":"query","name":"ageMin","required":false,"schema":{"type":"integer","minimum":0,"maximum":17}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole years; see ageMin.","in":"query","name":"ageMax","required":false,"schema":{"type":"integer","minimum":0,"maximum":17}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated camps, classes, after_school, mapped onto the activity's format and seasonality.","in":"query","name":"program","required":false,"schema":{"type":"string","example":"camps"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated feed interest ids (at most 20): offerings tagged with any of them.","in":"query","name":"interest","required":false,"schema":{"type":"string","example":"robotics,painting"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Origin for distanceMiles, radiusMiles and orderBy=distance: home (the household's searchArea.point; 409 when it is unset; 400 for the consumer site key) or latitude,longitude in decimal degrees.","in":"query","name":"near","required":false,"schema":{"type":"string","example":"47.61,-122.33"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Keeps rows whose own coordinates are within this many miles of near; rows without coordinates are excluded. Requires near.","in":"query","name":"radiusMiles","required":false,"schema":{"type":"integer","minimum":1,"maximum":50}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. A map's view: west,south,east,north in decimal degrees (west greater than east crosses the antimeridian; south below north). Keeps rows whose point (their own, else the one they inherit) is street-grade (coordinate_precision exact, building, parcel or street) and inside the view; a ZIP or state centre is never in view. Combines with every other filter, near, orderBy=distance and fields=pins; counts (limit=0) count the rows in view. 400 when it is not four finite numbers describing a nonempty view.","in":"query","name":"bbox","required":false,"schema":{"type":"string","example":"-122.75,45.45,-122.55,45.6"}},{"description":"Consumer site key only; any other credential gets 400 (a signed-in family reads in its household time zone). The visitor's IANA time zone (UTC or an Area/Location tz database name), which sets the day availability, nextSessions and date filters read from. Without it, signed-out reads use the launch metro's zone (America/Los_Angeles). An unknown or process-dependent name (Local) is 400.","in":"query","name":"tz","required":false,"schema":{"type":"string","example":"America/Los_Angeles"}},{"description":"Case-insensitive exact city name or its lowercase hyphenated slug.","in":"query","name":"city","required":false,"schema":{"example":"seattle","type":"string"}},{"description":"Case-insensitive exact region, such as a state abbreviation.","in":"query","name":"region","required":false,"schema":{"example":"WA","type":"string"}},{"description":"Exact category recorded on this session; no parent inheritance.","in":"query","name":"category","required":false,"schema":{"example":"STEM","type":"string"}},{"description":"Returns records whose stated whole completed age range in inclusive age_range_years bounds includes this age in years.","in":"query","name":"ageYears","required":false,"schema":{"example":10,"maximum":8333,"minimum":0,"type":"integer"}},{"description":"Legacy compatibility filter for month-based ages. New clients should use whole-year ageYears; when a whole-year range is sent through a legacy month-shaped adapter, inclusive bounds expand as [yearMin*12,yearMax*12+11].","in":"query","name":"ageMonths","required":false,"schema":{"example":120,"maximum":100000,"minimum":0,"type":"integer"}},{"description":"Returns records whose stated grade range includes this grade. Use -1 for pre-K and 0 for kindergarten.","in":"query","name":"grade","required":false,"schema":{"example":5,"maximum":12,"minimum":-1,"type":"integer"}},{"description":"Inclusive minimum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response.","in":"query","name":"minimumScore","required":false,"schema":{"example":81,"maximum":100,"minimum":0,"type":"integer"}},{"description":"Inclusive maximum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response. Must not be below minimumScore.","in":"query","name":"maximumScore","required":false,"schema":{"example":100,"maximum":100,"minimum":0,"type":"integer"}},{"name":"filter","in":"query","required":false,"description":"One URL-encoded JSON predicate tree, sent once. Combines with all legacy filters using AND before rows, totals and facet counts are selected. Read /api/canonical/schema tables[*].filterCapabilities for visible fields, operators, relations and limits. Invalid/unknown/over-complex filters return 400; unavailable fields return 403, including in nonmatching branches. Do not broaden a failed request by dropping predicates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalFilter"}}}},{"name":"organizationId","in":"query","required":false,"description":"Exact organization UUID. Retained legacy membership filter accepts the session direct organization or its activity organization. Use a related organization predicate for direct-owner-first semantics.","schema":{"type":"string","format":"uuid"}},{"name":"activityId","in":"query","required":false,"description":"Exact session activity UUID.","schema":{"type":"string","format":"uuid"}},{"name":"registrationStatus","in":"query","required":false,"description":"Exact recorded registration_status when visible at the current plan.","schema":{"type":"string"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated format kinds (day_camp, overnight_camp, class, drop_in, event): activities whose formatKind is any of them (a session through its activity), read from activity_format_facts. An unknown value is 400.","in":"query","name":"formatKind","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Existing success envelope containing facets, tier, resultLimit, and sourceClass. A single field returns values with value, label, and rows. Without field (all facets), each free-text facet (city, region, category, skillLevel, format, seasonality) lists at most facetValueLimit values (default 100, a tunable policy), its most common by count; a value the request filters that field on is always listed, first. facetValueLimit is in the envelope. Read one facet with field=, limit and q for the rest."},"400":{"description":"Invalid field or snapshot option. Also returned for malformed, repeated, unknown, over-complex or incorrectly typed structured filter JSON."},"401":{"description":"Authentication required."},"402":{"description":"Starter or higher required."},"409":{"description":"near=home when the family's household has neither a searchArea.point nor a known searchArea.postalCode (a co-parent's or caregiver's home is the family owner's)."},"429":{"description":"Request allowance exceeded, with Retry-After. A data-API key's hourly read allotment answers retryAfterMs, readsAllotment and windowMs. A signed-in family session or the consumer site key answers its per-minute app limit with retryAfterMs and windowMs only (the FamilyRequestLimit shape)."},"403":{"description":"A structured filter names a field or relationship outside current tier or preview disclosure."}},"summary":"Read sessions facet or chart counts","tags":["Canonical"],"x-auth":"Account or named integration API key on Starter or above. Facet fields follow the owner’s current plan. A consumer's own signed-in account reads the family facets at any tier, from sign-up on. The parent site's catalog-only consumer site key (SCHOOLS_OUT_CATALOG_API_KEY) also reads it for signed-out visitors, as a Free family with no household: the family fields only, near as latitude,longitude (near=home is 400), and the day in the visitor's tz (default: the launch metro's zone).","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/sessions/facets\" -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" --data-urlencode \"field=city\" --data-urlencode \"snapshot=true\"","title":"Read sessions chart counts"}]}},"/api/sessions/{id}":{"get":{"description":"Returns the tier-masked session and parent summaries. Sessions own registration_open_date, registration_close_date, and ordered `prices` entries with nullable `price`, `basis`, and `currency`; an entry cannot be all null, `[]` means no recorded price, and zero means explicitly free. Nullable `discounts` holds freeform terms without changing prices or the early-bird flag. Care quotes have independent amount, currency and basis. Pricing remains tier-masked, including nested schedule quotes. schedule_configurations owns session dates and clocks. Each group selects explicit dates, a bounded weekly date_rule with source-stated weekdays and exclusions, or a sole date_range for independently known bounds without known attendance dates. time_kind distinguishes daily, arrival_departure, and unspecified clocks. Effective attendance dates cannot overlap; rules remain rules in responses and ranges never imply attendance. Public scalar dates and clocks are derived read-only summaries. Clearing the collection leaves the schedule unknown. Normal JSON records omit bookkeeping and ingestion history, null and blank record fields, and unknown availability, registration-status, and coordinate-precision values. Zero, false, empty arrays, masked values, positional range bounds, and structured schedule states retain their meanings. Related entity summaries follow the same projection; CSV retains its existing contract. Nonblank configuration_id is retained as record identity metadata for feedback targeting and omitted from display column metadata. Moved fields are external_ids, created_at, updated_at, source_created_at, field_provenance, source_records, merge_jobs, and relationship_jobs. Read permitted bookkeeping and provenance at /api/sessions/{id}/provenance. Age ranges use age_range_years; normal JSON records and column metadata omit the redundant age_range_months alias. CSV and the legacy ageMonths filter retain month compatibility. Records and column metadata omit score. Read /api/sessions/{id}/score for live detail or /api/sessions/scores?ids=UUID,UUID&cached=true for persisted summaries. Normal detail responses include data.media, the first gallery page of up to 100 images, with entity, items, coverId, logoId, total, limit, and offset. Images include displayUrl, label, kind, width, and height; source attribution is omitted even for ADMIN. Previews omit media. Optional includeMedia=false skips the gallery; omitted or true includes it. The parameter accepts one exact true or false value, with malformed or repeated values returning 400. Use the existing /media endpoint for additional pages or authorized source attribution. On /media, includeProvenance=false skips source attribution; omitted or true retains existing authority-gated sources. Use limit=1 at the selected image offset when requesting only its source history. The record's independent overnight boolean is true for explicit overnight status and false for explicit day status; unknown or mixed status is omitted. It never inherits a parent's status. A consumer's own signed-in account reads at any tier, from sign-up on with the family fields added: data.session carries its tier's fields (none below Starter) plus the FamilyCatalogRecord fields and derived availability; data.projection is family; lockedColumns lists what the tier locks and the family fields do not show. Ordinary permitted JSON records include resolved_cover with the ultimate source entity, inheritance flag, and structured stock attribution. Owned gallery and image compatibility fields retain their existing meaning. Detail responses include data.tier and data.lockedColumns for this record kind without requiring a list read; preview and family reads retain their existing disclosure overrides.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"name":"includeMedia","in":"query","required":false,"description":"One exact true or false value. Omitted or true bundles the first gallery page; false skips media for follow-up diagnostic reads. Previews always omit media.","schema":{"type":"boolean","default":true}},{"description":"Consumer site key only; any other credential gets 400 (a signed-in family reads in its household time zone). The visitor's IANA time zone (UTC or an Area/Location tz database name), which sets the day availability, nextSessions and date filters read from. Without it, signed-out reads use the launch metro's zone (America/Los_Angeles). An unknown or process-dependent name (Local) is 400.","in":"query","name":"tz","required":false,"schema":{"type":"string","example":"America/Los_Angeles"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"name":"includeResolution","in":"query","required":false,"description":"Include field_resolution on each returned entity row: own, inherited, or unset for each projected field. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeSources","in":"query","required":false,"description":"Include field_sources on each returned entity row, mapping fields to the canonical entity supplying their effective value or configuration unit. Independent of includeResolution; defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeOwnValues","in":"query","required":false,"description":"Include own_values on each returned entity row, retaining null local values. Requires editor authorization; otherwise returns 403. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"session":{"anyOf":[{"$ref":"#/components/schemas/CanonicalDetailRow"},{"$ref":"#/components/schemas/FamilyCatalogRecord"}]},"media":{"$ref":"#/components/schemas/CanonicalMediaPage"},"tier":{"type":"string"},"lockedColumns":{"type":"array","description":"Fields withheld for this record kind under the effective tier, preview, or family projection.","items":{"type":"object","required":["column","requiredTier"],"properties":{"column":{"type":"string"},"requiredTier":{"type":"string"}}}}},"required":["session","tier","lockedColumns"],"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Canonical session detail"},"404":{"$ref":"#/components/responses/NotFound"},"400":{"description":"Invalid detail parameters, including malformed or repeated includeMedia."},"429":{"description":"Request allowance exceeded, with Retry-After. A data-API key's hourly read allotment answers retryAfterMs, readsAllotment and windowMs. A signed-in family session or the consumer site key answers its per-minute app limit with retryAfterMs and windowMs only (the FamilyRequestLimit shape)."},"403":{"description":"showRemoved=true requires a current staff or ADMIN operator; preview and non-operator viewers cannot opt in."}},"summary":"Read one canonical session","tags":["Organization catalog"],"x-auth":"Account cookie or header, or permitted machine key, from Starter up. The parent site's catalog-only consumer site key (SCHOOLS_OUT_CATALOG_API_KEY) also reads it for signed-out visitors, as a Free family with no household: the family fields only, near as latitude,longitude (near=home is 400), and the day in the visitor's tz (default: the launch metro's zone).","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/sessions/$SESSION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Read one session"}]}},"/api/sessions/{id}/media":{"get":{"description":"A consumer's own signed-in account may read media at any tier. The consumer site key may not; its detail reads take includeMedia=true. Returns this record’s owned paginated gallery and selections plus resolvedCover for display. Inherited covers retain their owning record and do not change gallery membership or counts. Stock covers include public provider and photographer credits. Remote URLs and R2 URLs are served directly; uploaded assets retain protected URLs, except the selected logo (logoId), which once rendered is its public, cross-origin logo rendition URL (GET /api/media/logos/{file}). Source evidence is available only to non-preview ADMIN accounts. Pagination uses the existing catalog bounds. Authorized showRemoved=true retains this gallery and its selections for staff inspection. Protected uploaded URLs under /api/organizations/{id}/brand/assets/{assetId} remain readable by current operators after removal under the existing brand-read access checks; ordinary viewers cannot use those binary reads to inspect removed records.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Defaults to 30 when absent or invalid; values over 100 cap at 100.","in":"query","name":"limit","schema":{"default":30,"maximum":100,"minimum":1,"type":"integer"}},{"description":"Defaults to 0 when absent or invalid; values over 1000000 cap at 1000000.","in":"query","name":"offset","schema":{"default":0,"maximum":1000000,"minimum":0,"type":"integer"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/CanonicalMediaPage"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Paginated media and selected IDs"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Starter access is required."},"403":{"description":"Previews cannot read media."},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Read session media","tags":["Organization catalog"],"x-auth":"Starter or above; previews are denied. Resolved covers follow the same media access gates.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/sessions/$SESSION_ID/media\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"limit=30\" \\\n  --data-urlencode \"offset=0\"","title":"Read the first page of a session gallery"}]}},"/api/users/authenticate-interstitial":{"post":{"description":"Creates a 30-minute one-time code for the signed-in account. REDIRECT_SCHOOLS_OUT_INTELLIGENCE returns INTELLIGENCE_ORIGIN/auth-callback and accepts an optional challenge. REDIRECT_SCHOOLS_OUT_APP returns APP_AUTH_CALLBACK (default schoolsout://auth-callback) and requires a challenge, stored on the code. Outside production the app may pass callback exp://<host>:<port>/--/auth-callback for Expo Go; alternate callbacks are rejected in production; an omitted callback or the exact configured APP_AUTH_CALLBACK is accepted. Accounts without a verified email receive 403 unless they are ADMIN or staff.","requestBody":{"content":{"application/json":{"schema":{"properties":{"callback":{"description":"Optional exact configured APP_AUTH_CALLBACK. Outside production only, exp://<host>:<port>/--/auth-callback is also accepted.","type":"string"},"challenge":{"description":"Unpadded base64url SHA-256 of the client's verifier. Required for REDIRECT_SCHOOLS_OUT_APP.","maxLength":43,"minLength":43,"pattern":"^[A-Za-z0-9_-]{43}$","type":"string"},"domain":{"enum":["REDIRECT_SCHOOLS_OUT_INTELLIGENCE","REDIRECT_SCHOOLS_OUT_APP"],"type":"string"}},"required":["domain"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"code":{"format":"uuid","type":"string"},"existing":{"const":true,"type":"boolean"},"redirectUrl":{"type":"string"}},"required":["code","existing","redirectUrl"],"type":"object"}}},"description":"Code issued"},"400":{"description":"Unknown redirect id, invalid or missing challenge, or a rejected callback"},"401":{"description":"Current account required"},"403":{"description":"Verified email required"}},"security":[{"AccountCredential":[]}],"summary":"Issue a one-time code for a product callback","tags":["Internal sign-in handoff"],"x-auth":"A signed-in device: the session cookie naming a live session (sid), or the account key with X-Session-Id. A bare account key, machine keys and service delegation answer 401.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/authenticate-interstitial\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"domain\":\"REDIRECT_SCHOOLS_OUT_APP\",\"challenge\":\"BASE64URL_SHA256_OF_VERIFIER\"}'","title":"Issue an app sign-in code"}],"x-internal":true}},"/api/users/exchange-interstitial":{"post":{"description":"Consumes the code once, within 30 minutes of issue, and returns the identity plus sessionKey, the account credential the product keeps server-side or in device secure storage. A code issued with a challenge also requires verifier, and base64url(SHA-256(verifier)) must equal the challenge. Every attempt consumes the code, so a missing or wrong verifier cannot be retried.","requestBody":{"content":{"application/json":{"schema":{"properties":{"code":{"format":"uuid","type":"string"},"verifier":{"type":"string"}},"required":["code"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"sessionKey":{"type":"string"},"user":{"properties":{"email":{"type":["string","null"]},"id":{"type":"string"},"level":{"type":"integer"},"username":{"type":["string","null"]}},"type":"object"}},"required":["sessionKey","user"],"type":"object"}}},"description":"Session issued"},"400":{"description":"Invalid, expired, reused or unproven code"},"401":{"description":"Account unavailable or no longer verified"}},"security":[],"summary":"Exchange a one-time code for the account session","tags":["Internal sign-in handoff"],"x-auth":"Public; the one-time code and, for the app, its verifier are the credentials.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/exchange-interstitial\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"TEMPORARY_ACCESS_CODE\",\"verifier\":\"APP_PROOF_VERIFIER\"}'","title":"Exchange an app sign-in code"}],"x-internal":true}},"/api/users/magic-link":{"post":{"description":"Always answers success for a well-formed address and an admitted referralCode. intent (signin|signup) only changes the email copy. An address with no account that the sign-up gate refuses (global settings signups_enabled off, or referral_only on without a code) is emailed why instead of a link, and the response is the same, so it never says whether an address has an account. A referralCode that is malformed, unknown or used up is refused at once, since that says nothing about the address; it is stored with the link and redeemed by the account the link creates. redirect, domain and challenge are stored as the link's handoff and returned when POST /api/users/authenticate-code redeems it, so a link requested by the app finishes through the interstitial with the same challenge. domain REDIRECT_SCHOOLS_OUT_APP requires a challenge. The link always opens AUTH_ORIGIN.","requestBody":{"content":{"application/json":{"schema":{"properties":{"referralCode":{"description":"A household's referral code (case and surrounding space forgiven), required while the referral_only global setting is on. See GET /api/global-settings.","type":"string"},"challenge":{"description":"Unpadded base64url SHA-256 of the client's verifier. Required for REDIRECT_SCHOOLS_OUT_APP.","maxLength":43,"minLength":43,"pattern":"^[A-Za-z0-9_-]{43}$","type":"string"},"domain":{"type":"string"},"email":{"format":"email","type":"string"},"intent":{"enum":["signin","signup"],"type":"string"},"redirect":{"maxLength":2048,"type":"string"}},"required":["email"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"success":{"const":true,"type":"boolean"}},"required":["success"],"type":"object"}}},"description":"Link sent (or silently skipped)"},"404":{"description":"Unknown referralCode (code referral_not_found)."},"409":{"description":"referralCode has admitted as many families as it can (code referral_limit_reached)."},"400":{"description":"Invalid email, intent or challenge, or an app link without a challenge"},"429":{"description":"Address or client rate limit"}},"security":[],"summary":"Email a one-shot sign-in link","tags":["Internal sign-in handoff"],"x-auth":"Public.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/magic-link\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"you@example.com\",\"intent\":\"signin\",\"domain\":\"REDIRECT_SCHOOLS_OUT_APP\",\"challenge\":\"BASE64URL_SHA256_OF_VERIFIER\"}'","title":"Email an app sign-in link"}],"x-internal":true}},"/api/users/viewer/care-requirements":{"get":{"description":"Returns every saved requirement, including expired ones, with only the children the adult can currently access. Requirements are private to their owner even for shared children.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"careRequirements":{"items":{"$ref":"#/components/schemas/FamilyCareRequirement"},"type":"array"}},"required":["careRequirements"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"}},"security":[{"AccountCredential":[]}],"summary":"List the signed-in adult's dated care requirements","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/care-requirements\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List care requirements"}],"x-internal":true},"post":{"description":"Saves dates or a date range, required weekdays and local hours, time zone, alternate-week recurrence, exceptions, flexibility, coverage and optional budget/travel overrides for one to ten children the adult can access (owner or current guardian), up to 50 per account. Child access is locked and rechecked so a concurrent revocation is observed. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyCareRequirementInput"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"careRequirement":{"$ref":"#/components/schemas/FamilyCareRequirement"}},"required":["careRequirement"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"}},"security":[{"AccountCredential":[]}],"summary":"Save a dated care requirement for selected children","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/care-requirements\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"childIds\":[\"$CHILD_ID\"],\"label\":\"Summer coverage\",\"dates\":{\"start\":\"2027-06-21\",\"end\":\"2027-08-27\"},\"days\":[\"mon\",\"tue\",\"wed\",\"thu\",\"fri\"],\"hours\":{\"start\":\"08:30\",\"end\":\"17:30\"},\"flexibility\":{\"level\":\"fixed\"},\"coverage\":{\"mode\":\"every_required_day\"}}'","title":"Save summer weekday coverage"}],"x-internal":true}},"/api/users/viewer/care-requirements/{requirementId}":{"delete":{"description":"DELETE {version} removes the requirement and its child links. Stale versions return 409.","parameters":[{"in":"path","name":"requirementId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"version":{"minimum":1,"type":"integer"}},"required":["version"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"deleted":{"const":true}},"required":["deleted"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"}},"security":[{"AccountCredential":[]}],"summary":"Remove a care requirement","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/care-requirements/$REQUIREMENT_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":2}'","title":"Remove a care requirement"}],"x-internal":true},"patch":{"description":"Replaces named keys (childIds, label, dates, timezone, days, hours, recurrence, exceptions, flexibility, coverage, budget, travel); null removes an optional key. The merged requirement is validated completely and needs at least one currently permitted child. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","parameters":[{"in":"path","name":"requirementId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyCorrection"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"careRequirement":{"$ref":"#/components/schemas/FamilyCareRequirement"}},"required":["careRequirement"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"}},"security":[{"AccountCredential":[]}],"summary":"Correct named parts of a care requirement","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/care-requirements/$REQUIREMENT_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":1,\"changes\":{\"exceptions\":[{\"date\":\"2027-07-05\",\"needed\":false}]}}'","title":"Add a date without care needed"}],"x-internal":true}},"/api/users/viewer/care-requirements/{requirementId}/confirmations":{"post":{"description":"Records a fresh approval of named parts (children, label, dates, timezone, schedule, flexibility, coverage, budget, travel). The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","parameters":[{"in":"path","name":"requirementId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyReconfirmation"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"careRequirement":{"$ref":"#/components/schemas/FamilyCareRequirement"}},"required":["careRequirement"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"}},"security":[{"AccountCredential":[]}],"summary":"Reconfirm an unchanged care requirement","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/care-requirements/$REQUIREMENT_ID/confirmations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":2,\"keys\":[\"schedule\"]}'","title":"Reconfirm the schedule"}],"x-internal":true}},"/api/users/viewer/child-invitations/accept":{"post":{"description":"Current nondelegated account with a live verified email matching the invitation recipient. Wrong or unverified accounts receive 403 without child details; unavailable tokens return 404. POST {token,approve:true} consumes an available invitation once and grants guardian access in one transaction. The actor’s current email and verification state are locked, then the profile and invitation are locked and expiry/revocation rechecked. Returns data.child with role guardian. Guardians may read and correct current details/preferences and use owner-enabled registration reuse; they cannot delete the profile, change reuse permission, or manage other guardians. Profile or owner removal cascades invitations and memberships.","requestBody":{"content":{"application/json":{"schema":{"properties":{"approve":{"const":true,"type":"boolean"},"token":{"maxLength":43,"minLength":43,"pattern":"^[A-Za-z0-9_-]{43}$","type":"string"}},"required":["token","approve"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"child":{"$ref":"#/components/schemas/FamilyChildProfile"}},"required":["child"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid invitation input or missing explicit approval"},"401":{"description":"Current account required"},"403":{"description":"Owner-only action or current verified recipient email required"},"404":{"description":"Profile, guardian or invitation not available to this account"},"409":{"description":"Duplicate pending invitation or guardian limit"},"429":{"description":"Invitation creation rate limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"security":[{"AccountCredential":[]}],"summary":"Explicitly accept guardian access once","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/child-invitations/accept\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'","title":"Explicitly accept guardian access once"}],"x-internal":true}},"/api/users/viewer/child-invitations/preview":{"post":{"description":"Current nondelegated account with a live verified email matching the invitation recipient. Wrong or unverified accounts receive 403 without child details; unavailable tokens return 404. POST {token} returns the child name, inviting owner email, recipient address and invitation dates for review. It does not return saved profile fields or grant access. Tokens travel in the JSON body, not the API URL. Invitation landing links use /family/invitations#token.","requestBody":{"content":{"application/json":{"schema":{"properties":{"token":{"maxLength":43,"minLength":43,"pattern":"^[A-Za-z0-9_-]{43}$","type":"string"}},"required":["token"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"invitation":{"$ref":"#/components/schemas/ChildInvitationPreview"}},"required":["invitation"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid invitation input or missing explicit approval"},"401":{"description":"Current account required"},"403":{"description":"Owner-only action or current verified recipient email required"},"404":{"description":"Profile, guardian or invitation not available to this account"},"409":{"description":"Duplicate pending invitation or guardian limit"},"429":{"description":"Invitation creation rate limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"security":[{"AccountCredential":[]}],"summary":"Review an invitation addressed to your verified email","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/child-invitations/preview\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'","title":"Review an invitation addressed to your verified email"}],"x-internal":true}},"/api/users/viewer/children":{"get":{"description":"Current account cookie or account credential only; machine keys and delegated service requests are rejected. Lists owned child profiles and profiles with an accepted, active guardian membership, with role owner or guardian. Each profile includes its registration fields, planning details, interests and preference notes, current version and current-value provenance. Provenance records manual approval or an approved Assistant suggestion, the actor and approval time; another adult's conversation reference is omitted. No previous values are retained. Connected-service imports are not supported. With include=school each child also carries its school (E-55's shape, null when unanswered), read for every child in one query.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"children":{"items":{"allOf":[{"$ref":"#/components/schemas/FamilyChildProfile"},{"properties":{"school":{"description":"Only with include=school: the child's school (as GET .../children/{childId}/school), or null.","oneOf":[{"$ref":"#/components/schemas/FamilyPlanChildSchool"},{"type":"null"}]}},"type":"object"}]},"type":"array"}},"required":["children"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid profile input, or an include other than school"},"401":{"description":"Current account required"},"404":{"description":"Profile not found for this owner"},"409":{"description":"Stale version or profile count limit"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"List profiles you own or have accepted guardian access to","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/children\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List profiles you own or have accepted guardian access to"}],"x-internal":true,"parameters":[{"name":"include","in":"query","required":false,"schema":{"enum":["school"],"type":"string"},"description":"school adds each child's school (null when the parent has not answered), read for all children at once."}]},"post":{"description":"Creates one of up to 20 owned profiles from approved values. A name is optional (a child may be named later); registration reuse needs both names and defaults to false. Fields are canonical registration strings; preferences hold interests with an unknown/none/listed status and notes; planning holds structured planning details. Each current value records source, actor, approval time and version. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyChildInput"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"child":{"$ref":"#/components/schemas/FamilyChildProfile"}},"required":["child"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid profile input"},"401":{"description":"Current account required"},"404":{"description":"Profile not found for this owner"},"409":{"description":"Stale version or profile count limit"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not add or edit children (edit_children; a caregiver)"}},"security":[{"AccountCredential":[]}],"summary":"Create a manually approved child profile","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/children\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'","title":"Create a manually approved child profile"}],"x-internal":true}},"/api/users/viewer/children/{childId}":{"delete":{"description":"DELETE {version} permanently removes the profile, guardian memberships, invitations, care requirement links and child-linked remembered facts; submitted registration snapshots and receipts remain. Owner only. Stale versions return 409. When the child has a non-closed booking or plan dated today or later, answers 409 with code child-has-plans and plans: FamilyAccountUpcomingPlan[] in the error body, so the app can list them without another call.","parameters":[{"in":"path","name":"childId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"version":{"minimum":1,"type":"integer"}},"required":["version"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"deleted":{"const":true,"type":"boolean"}},"required":["deleted"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid profile input"},"401":{"description":"Current account required"},"404":{"description":"Profile not found for this owner"},"409":{"description":"Stale version, or child-has-plans with the blocking plans"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not add or edit children (edit_children; a caregiver)"}},"security":[{"AccountCredential":[]}],"summary":"Permanently remove the reusable profile; existing registration snapshots remain","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":1}'","title":"Permanently remove the reusable profile; existing registration snapshots remain"}],"x-internal":true},"patch":{"description":"Applies a narrow correction to fields, preferences.notes, preferences.interests ({status, values}), planning.label, planning.age, planning.gradeSchoolYear or planning.requirements ({status, items}); null removes a value. reuseAllowed cannot be changed here. Owner or current guardian; locks the actor and profile and rechecks membership before saving. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","parameters":[{"in":"path","name":"childId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyCorrection"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"child":{"$ref":"#/components/schemas/FamilyChildProfile"}},"required":["child"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not add or edit children (edit_children; a caregiver)"}},"security":[{"AccountCredential":[]}],"summary":"Correct named child profile values without replacing the profile","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":3,\"changes\":{\"child.allergies\":null,\"planning.requirements\":{\"status\":\"none\",\"items\":[]}}}'","title":"Remove an allergy note and record that there are no participation requirements"}],"x-internal":true},"put":{"description":"PUT fully replaces fields and preferences with the current version; omitted or blank optional fields are removed. Omitting planning keeps the saved planning so older clients cannot clear it; sending planning replaces it. Only the owner may change reuseAllowed. Unchanged values retain provenance; changed values record the acting account, source and new version. Stale versions return 409. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","parameters":[{"in":"path","name":"childId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/FamilyChildInput"},{"required":["version"],"type":"object"}]}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"child":{"$ref":"#/components/schemas/FamilyChildProfile"}},"required":["child"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid profile input"},"401":{"description":"Current account required"},"404":{"description":"Profile not found for this owner"},"409":{"description":"Stale version or profile count limit"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not add or edit children (edit_children; a caregiver)"}},"security":[{"AccountCredential":[]}],"summary":"Correct an owned or shared child profile using its current version","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'","title":"Correct an owned or shared child profile using its current version"}],"x-internal":true}},"/api/users/viewer/children/{childId}/confirmations":{"post":{"description":"Records a fresh approval of the named current values (provenance keys such as guardian.email, preferences.interests or planning.age) without changing them. Owner or current guardian. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","parameters":[{"in":"path","name":"childId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyReconfirmation"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"child":{"$ref":"#/components/schemas/FamilyChildProfile"}},"required":["child"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"Reconfirm unchanged child profile values","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/confirmations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":4,\"keys\":[\"child.grade\",\"planning.gradeSchoolYear\"]}'","title":"Reconfirm the saved grade and its school year"}],"x-internal":true}},"/api/users/viewer/children/{childId}/guardians":{"get":{"description":"Current profile owner only; machine, service and delegated-service credentials are rejected. Cookie mutations require CSRF. GET returns active guardian accounts and unexpired pending invitations. It never returns invitation tokens, token hashes, or acceptance links. A profile has at most five active or pending guardians in addition to its owner.","parameters":[{"in":"path","name":"childId","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"guardians":{"items":{"$ref":"#/components/schemas/ChildGuardian"},"type":"array"},"invitations":{"items":{"$ref":"#/components/schemas/ChildInvitation"},"type":"array"}},"required":["guardians","invitations"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid invitation input or missing explicit approval"},"401":{"description":"Current account required"},"403":{"description":"Owner-only action or current verified recipient email required"},"404":{"description":"Profile, guardian or invitation not available to this account"},"409":{"description":"Duplicate pending invitation or guardian limit"},"429":{"description":"Invitation creation rate limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"security":[{"AccountCredential":[]}],"summary":"Review guardian access and pending invitations","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Owner-only guardian management; API_ASSISTANT_ORIGINS are not trusted on these routes.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/guardians\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Review guardian access and pending invitations"}],"x-internal":true}},"/api/users/viewer/children/{childId}/guardians/{userId}":{"delete":{"description":"Current profile owner only; machine, service and delegated-service credentials are rejected. Cookie mutations require CSRF. DELETE removes this child’s guardian membership. Future profile reads, corrections and registration reuse recheck membership and refuse after revocation. Other children and existing registration receipts are unchanged.","parameters":[{"in":"path","name":"childId","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"userId","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"revoked":{"const":true,"type":"boolean"}},"required":["revoked"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid invitation input or missing explicit approval"},"401":{"description":"Current account required"},"403":{"description":"Owner-only action or current verified recipient email required"},"404":{"description":"Profile, guardian or invitation not available to this account"},"409":{"description":"Duplicate pending invitation or guardian limit"},"429":{"description":"Invitation creation rate limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"security":[{"AccountCredential":[]}],"summary":"Remove guardian access","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Owner-only guardian management; API_ASSISTANT_ORIGINS are not trusted on these routes.","x-curl-examples":[{"code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/guardians/$USER_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'","title":"Remove guardian access"}],"x-internal":true}},"/api/users/viewer/children/{childId}/invitations":{"post":{"description":"Current profile owner only; machine, service and delegated-service credentials are rejected. Cookie mutations require CSRF. POST {email} creates an invitation valid for seven days. The response reveals inviteUrl once and reports emailSent. Configured email delivery is attempted after commit; failed or unavailable email delivery leaves a usable copy link. The fragment contains 32 random bytes encoded as 43 URL-safe base64 characters; only its SHA-256 hash is stored. The invitee must verify the matching current account email, review the invitation, and explicitly accept. Creating an invitation grants no access. Duplicate pending addresses or the five-guardian limit return 409; creation is limited to 20 invitations per account per hour. An allow-listed redirect makes the link open that page (#<token>) instead of Intelligence's.","parameters":[{"in":"path","name":"childId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"email":{"format":"email","maxLength":254,"type":"string"},"redirect":{"description":"Optional page the emailed link opens, with the token in its fragment: an absolute URL on the Auth app's or the parent site's origin (AUTH_ORIGIN, PARENT_SITE_ORIGIN, API_ASSISTANT_ORIGINS). Anything else, or none, links to Intelligence's /family/invitations.","format":"uri","type":"string"}},"required":["email"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"emailSent":{"type":"boolean"},"invitation":{"$ref":"#/components/schemas/ChildInvitation"},"inviteUrl":{"description":"Revealed once; token lives in the URL fragment.","format":"uri","type":"string"}},"required":["invitation","inviteUrl","emailSent"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid invitation input or missing explicit approval"},"401":{"description":"Current account required"},"403":{"description":"Owner-only action or current verified recipient email required"},"404":{"description":"Profile, guardian or invitation not available to this account"},"409":{"description":"Duplicate pending invitation or guardian limit"},"429":{"description":"Invitation creation rate limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"security":[{"AccountCredential":[]}],"summary":"Invite another guardian to this child profile","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Owner-only guardian management; API_ASSISTANT_ORIGINS are not trusted on these routes.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/invitations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'","title":"Invite another guardian to this child profile"}],"x-internal":true}},"/api/users/viewer/children/{childId}/invitations/{invitationId}":{"delete":{"description":"Current profile owner only; machine, service and delegated-service credentials are rejected. Cookie mutations require CSRF. DELETE revokes an unaccepted invitation for this child. Acceptance and revocation serialize on the profile, so a revoked invitation cannot later grant access. Use the guardian removal route after an invitation has been accepted.","parameters":[{"in":"path","name":"childId","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"invitationId","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"revoked":{"const":true,"type":"boolean"}},"required":["revoked"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid invitation input or missing explicit approval"},"401":{"description":"Current account required"},"403":{"description":"Owner-only action or current verified recipient email required"},"404":{"description":"Profile, guardian or invitation not available to this account"},"409":{"description":"Duplicate pending invitation or guardian limit"},"429":{"description":"Invitation creation rate limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"security":[{"AccountCredential":[]}],"summary":"Revoke an invitation","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Owner-only guardian management; API_ASSISTANT_ORIGINS are not trusted on these routes.","x-curl-examples":[{"code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/invitations/$INVITATION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'","title":"Revoke an invitation"}],"x-internal":true}},"/api/users/viewer/family-context":{"get":{"description":"Returns version family-context-v1: permitted children (label, computed age or bounded reported age, grade with school-year currency, interests, required and preferred participation conditions, preference notes), the adult's own household preferences, active and upcoming care requirements with their effective time zone and inherited budget/travel, hard conflicts that need clarification, up to 25 saved tracking options (not enrollment or availability), up to 20 of the adult's own School's Out registrations for permitted children (complete:false; not a calendar), calendar: the day-detail composition of GET /api/users/viewer/calendar from the household's today for 21 days (per permitted child: school state and label, cover state, and school, plan, pending, gap and flag entries with household clock times; the family's cached Google events, other adults' as untitled busy blocks), without catalog suggestions or friends, with status not_permitted and no days for a member whose role may not read plans (null when planning is not wired), unexpired approved facts, and per-value source, approval time and version. Omits last names, birthdates, allergies, registration notes (child.notes), guardian and emergency contacts, reuse permission, invitations and account IDs. Approved preference notes remain in the projection. Generic table operations and research Ask never read these records. Booked-session context includes the ordered `prices` collection, scheduleConfigurations, and independently recorded care quotes. Effective date bounds and uniform clocks summarize explicit configurations; sparse dates never imply daily attendance.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"familyContext":{"type":"object"}},"required":["familyContext"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"}},"security":[{"AccountCredential":[]}],"summary":"Read the bounded family context the parent Assistant may use","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/family-context\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read the Assistant family context"}],"x-internal":true}},"/api/users/viewer/family-memory":{"get":{"description":"Returns the adult's explicitly approved facts. Child-linked facts appear only while the adult can access that child.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"facts":{"items":{"$ref":"#/components/schemas/FamilyMemoryFact"},"type":"array"}},"required":["facts"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"}},"security":[{"AccountCredential":[]}],"summary":"List approved remembered facts","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/family-memory\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List remembered facts"}],"x-internal":true},"post":{"description":"Requires approve:true and the exact statement (at most 500 characters), a category, an optional permitted child and an optional future expiry, up to 100 per account. A conversation mentioning something never saves it. Use structured profile, household and care fields instead of facts for values they cover. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"approve":{"const":true},"category":{"enum":["activity_history","logistics","preference","other"]},"childId":{"format":"uuid","type":"string"},"expiresOn":{"format":"date","type":"string"},"source":{"$ref":"#/components/schemas/FamilyApprovalSource"},"statement":{"maxLength":500,"minLength":1,"type":"string"}},"required":["approve","statement","category"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"fact":{"$ref":"#/components/schemas/FamilyMemoryFact"}},"required":["fact"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"}},"security":[{"AccountCredential":[]}],"summary":"Remember an explicitly approved statement","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/family-memory\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"approve\":true,\"statement\":\"Loved the pottery camp last summer\",\"category\":\"activity_history\",\"childId\":\"$CHILD_ID\"}'","title":"Remember an approved activity history fact"}],"x-internal":true}},"/api/users/viewer/family-memory/{factId}":{"delete":{"description":"DELETE {version} permanently removes the fact. Stale versions return 409.","parameters":[{"in":"path","name":"factId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"version":{"minimum":1,"type":"integer"}},"required":["version"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"deleted":{"const":true}},"required":["deleted"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"}},"security":[{"AccountCredential":[]}],"summary":"Forget a remembered fact","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/family-memory/$FACT_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":2}'","title":"Forget a remembered fact"}],"x-internal":true},"patch":{"description":"Replaces statement, category, childId or expiresOn; null removes childId or expiresOn. The new approval replaces the previous source reference. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","parameters":[{"in":"path","name":"factId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyCorrection"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"fact":{"$ref":"#/components/schemas/FamilyMemoryFact"}},"required":["fact"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"}},"security":[{"AccountCredential":[]}],"summary":"Correct a remembered fact","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/family-memory/$FACT_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":1,\"changes\":{\"statement\":\"Loved the pottery camp in 2026\"}}'","title":"Correct a remembered statement"}],"x-internal":true}},"/api/users/viewer/family-memory/{factId}/confirmations":{"post":{"description":"Renews approval of the current statement; an expired fact must be corrected with a new expiry instead. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","parameters":[{"in":"path","name":"factId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"source":{"$ref":"#/components/schemas/FamilyApprovalSource"},"version":{"minimum":1,"type":"integer"}},"required":["version"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"fact":{"$ref":"#/components/schemas/FamilyMemoryFact"}},"required":["fact"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"}},"security":[{"AccountCredential":[]}],"summary":"Reconfirm an unchanged remembered fact","tags":["Internal family planning"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/family-memory/$FACT_ID/confirmations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":2}'","title":"Reconfirm a remembered fact"}],"x-internal":true}},"/api/users/viewer/gmail":{"delete":{"description":"GET returns { connected, emailAddress, scope, connected_at } for the viewer's own Gmail grant, with nulls when no inbox is connected; the token is never returned. DELETE revokes the refresh token at Google, then deletes the grant and returns { connected: false, revoked }. A token Google already considers invalid counts as revoked. Any other Google failure returns 502 and keeps the grant so the request can be retried; a grant reconnected during the request returns 409.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"connected":{"const":false},"revoked":{"description":"Whether Google confirmed the grant revoked or already invalid.","type":"boolean"}},"required":["connected","revoked"],"type":"object"}}},"description":"Success. Values are top-level; no provider token is returned."},"400":{"description":"Invalid, expired or reused flow, missing proof, or rejected Google grant."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"409":{"description":"A grant was reconnected during revocation; retry against current state."},"501":{"description":"Gmail OAuth and token-sealing settings are not configured."},"502":{"description":"Google revocation failed; the stored connection is retained."}},"security":[{"AccountCredential":[]}],"summary":"Revoke and remove the Gmail grant","tags":["Internal Gmail"],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/gmail\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Disconnect Gmail"}],"x-internal":true},"get":{"description":"GET returns { connected, emailAddress, scope, connected_at } for the viewer's own Gmail grant, with nulls when no inbox is connected; the token is never returned. DELETE revokes the refresh token at Google, then deletes the grant and returns { connected: false, revoked }. A token Google already considers invalid counts as revoked. Any other Google failure returns 502 and keeps the grant so the request can be retried; a grant reconnected during the request returns 409.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"connected":{"type":"boolean"},"connected_at":{"description":"Original connection time, or null when disconnected.","format":"date-time","type":["string","null"]},"emailAddress":{"description":"The authorized Gmail address, or null when disconnected.","type":["string","null"]},"scope":{"description":"Granted Google scopes, or null when disconnected.","type":["string","null"]}},"required":["connected","emailAddress","scope","connected_at"],"type":"object"}}},"description":"Success. Values are top-level; no provider token is returned."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."}},"security":[{"AccountCredential":[]}],"summary":"Read the connected Gmail address","tags":["Internal Gmail"],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/gmail\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read the Gmail connection"}],"x-internal":true}},"/api/users/viewer/gmail/authorization":{"post":{"description":"Starts Gmail access and returns {url}. Web requests use an optional parent-site redirect and receive the browser nonce cookie. Proof requests send {redirect, challenge}: the app's exact schoolsout://gmail-callback (or exp://host:port/--/gmail-callback outside production), or a parent-site page when the site's server, not the browser, calls the API (a BFF), with an unpadded base64url SHA-256 proof of a verifier the client keeps. Their URL is a one-use 10-minute API browser bootstrap that sets its own cookie in the browser before Google consent; the client then completes with the verifier. No account key or verifier belongs in that URL.","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"challenge":{"description":"Unpadded base64url SHA-256 of the app-held verifier. Required for native callbacks and forbidden for web callbacks.","pattern":"^[A-Za-z0-9_-]{43}$","type":"string"},"redirect":{"description":"Allowed parent page for web, fixed schoolsout://gmail-callback for installed apps, or the narrow Expo Go callback in development.","maxLength":2048,"type":"string"}},"required":[],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"url":{"description":"Google consent URL for web; one-use API browser bootstrap URL for native.","format":"uri","type":"string"}},"required":["url"],"type":"object"}}},"description":"Success. Values are top-level; no provider token is returned."},"400":{"description":"Invalid, expired or reused flow, missing proof, or rejected Google grant."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"501":{"description":"Gmail OAuth and token-sealing settings are not configured."}},"security":[{"AccountCredential":[]}],"summary":"Start a browser or app Gmail connection","tags":["Internal Gmail"],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/gmail/authorization\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"redirect\":\"schoolsout://gmail-callback\",\"challenge\":\"$CHALLENGE\"}'","title":"Start a Gmail connection from the installed app"}],"x-internal":true}},"/api/users/viewer/gmail/complete":{"post":{"description":"Native completion accepts {code, verifier}. Consumes the five-minute ticket only for its starting adult; the original S256 proof must match a 43–128 character verifier. Exchanges the sealed Google authorization code and saves the grant only after validation, then returns top-level {connected, emailAddress, scope, connected_at}. Missing/wrong proof, expiry and replay return 400; a different adult cannot consume the ticket. No token is returned.","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"code":{"description":"One-time completion ticket returned to the app, not the Google code.","format":"uuid","type":"string"},"verifier":{"description":"Original app-held proof verifier, sent only in this authenticated body.","maxLength":128,"minLength":43,"type":"string"}},"required":["code","verifier"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"connected":{"type":"boolean"},"connected_at":{"description":"Original connection time, or null when disconnected.","format":"date-time","type":["string","null"]},"emailAddress":{"description":"The authorized Gmail address, or null when disconnected.","type":["string","null"]},"scope":{"description":"Granted Google scopes, or null when disconnected.","type":["string","null"]}},"required":["connected","emailAddress","scope","connected_at"],"type":"object"}}},"description":"Success. Values are top-level; no provider token is returned."},"400":{"description":"Invalid, expired or reused flow, missing proof, or rejected Google grant."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"501":{"description":"Gmail OAuth and token-sealing settings are not configured."}},"security":[{"AccountCredential":[]}],"summary":"Complete a native Gmail connection with app proof","tags":["Internal Gmail"],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/gmail/complete\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"$COMPLETION_TICKET\",\"verifier\":\"$VERIFIER\"}'","title":"Complete a native Gmail connection"}],"x-internal":true}},"/api/users/viewer/household":{"delete":{"description":"DELETE {version} erases household preferences and provenance and advances the retained revision. Stale versions return 409. GET returns empty preferences with the new revision; resaving requires that current revision.","requestBody":{"content":{"application/json":{"schema":{"properties":{"version":{"minimum":1,"type":"integer"}},"required":["version"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"deleted":{"const":true}},"required":["deleted"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"Only the family's owner changes the household (manage_household)"}},"security":[{"AccountCredential":[]}],"summary":"Remove all household preferences","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/household\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":2}'","title":"Remove household preferences"}],"x-internal":true},"get":{"description":"Returns the signed-in adult’s private household preferences, provenance, and version. Version 0 means never saved. After deletion, preferences and provenance are empty but the incremented version remains; use this version to save again.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"household":{"$ref":"#/components/schemas/FamilyHousehold"}},"required":["household"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not see the household (read_household; a caregiver)"}},"security":[{"AccountCredential":[]}],"summary":"Read the signed-in adult's household preferences","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/household\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read household preferences"}],"x-internal":true},"patch":{"description":"Version 0 saves the first preferences; later changes need the current version. Each named key (searchArea, timezone, budget, travel, caregiverAvailability, emergencyContacts, planningIntent, home, places) is replaced; null removes it. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval. After deletion, read and submit the retained current version; version 0 is only valid before the first save. searchArea is either {label, postalCode?, region?, radiusMiles?} or {placeId, radiusMiles?} with a placeId from GET /api/users/viewer/places, which the server replaces with the resolved label, postal code, region and point; a client-sent point is refused. A distance-only change, searchArea {radiusMiles}, keeps the stored area (label, postal code, region and point) and changes its radius; with no stored area it is 400. 404 means the placeId is unknown, expired or another viewer's. home is {placeId, label?}, which the server replaces with the lookup's label (or the label sent), address, point and precision; a label-only change, home {label}, renames the stored home. places is the whole list of at most 10 saved places: {placeId, kind, label?, childId?} adds one located from its lookup, {id, kind?, label?, childId?} keeps a saved one and its location. A client-sent point, address or precision is refused for both. kind is work, school or other; childId names one of the family's children, only on a school place, at most once. searchArea gains fromHome when its point is the home's.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyCorrection"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"household":{"$ref":"#/components/schemas/FamilyHousehold"}},"required":["household"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Unknown or expired placeId"},"409":{"description":"Stale version, limit, or nothing to reconfirm"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"Only the family's owner changes the household (manage_household)"}},"security":[{"AccountCredential":[]}],"summary":"Save or correct named household preferences","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/household\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":0,\"changes\":{\"budget\":{\"amount\":450,\"currency\":\"USD\",\"period\":\"week\",\"scope\":\"per_child\",\"limit\":\"hard_cap\"}}}'","title":"Save a weekly per-child hard budget cap"}],"x-internal":true}},"/api/users/viewer/household/confirmations":{"post":{"description":"Records a fresh approval of the named saved preferences without changing them. The authenticated viewer is always the approving actor. Optional source {type:\"manual\"} or {type:\"assistant\", conversationId, turnId} names one of the viewer's own completed Assistant turns that proposed the change; the write itself is the explicit approval.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyReconfirmation"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"household":{"$ref":"#/components/schemas/FamilyHousehold"}},"required":["household"],"type":"object"},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Current account required"},"404":{"description":"Record not found for this account"},"409":{"description":"Stale version, limit, or nothing to reconfirm"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"Only the family's owner changes the household (manage_household)"}},"security":[{"AccountCredential":[]}],"summary":"Reconfirm unchanged household preferences","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult; sharing a child never shares them.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/household/confirmations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":2,\"keys\":[\"timezone\"]}'","title":"Reconfirm the household time zone"}],"x-internal":true}},"/api/users/viewer/registrations":{"get":{"description":"Requires an account session or account credential belonging to the parent. Machine keys, service and delegated-service credentials are rejected. Cookie mutations require the current CSRF token. Responses are private and not cached.","responses":{"200":{"description":"Latest 200 owner-scoped registrations, newest first; data.registrations carries id, organizationId, organizationName, childProfileId (nullable after removal), profileVersion, confirmationCode, status, createdAt and authorization. Authorization carries version, purpose, approvedAt, sharedFields, reusedFields, fieldLabels and consentStatements. Previously submitted registrations remain after profile removal."},"401":{"description":"Sign-in required"},"403":{"description":"Account credential required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"List your registration disclosure receipts","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Registration receipts belong to the submitting adult.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/registrations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List your registration disclosure receipts"}],"x-internal":true}},"/api/users/viewer/registrations/{id}":{"post":{"description":"Current account owns the child profile or has accepted active guardian access; cookie mutations require CSRF. Machine keys and service impersonation are rejected. Registers with camp id using an explicitly approved saved profile. Send the normal registration body plus profile {id,version}, approveSharing:true and consentStatements [{key,label,text}] matching each accepted form acknowledgement. The transaction locks the submitting account and profile, then rechecks live owner or guardian access, version and owner-controlled reuse permission before allocating capacity. The receipt belongs to the submitting account; joining a profile does not grant access to another account’s registration history. Corrections never rewrite submitted registrations. Stale profile/consent returns 409; inaccessible profiles return 404. Guest registration remains at POST /api/organizations/{id}/registrations.","parameters":[{"description":"Destination organization ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"description":"Canonical and custom answers follow the destination’s public registration form. Only visible requested fields are accepted. Consent statements must exactly match accepted acknowledgements in form order. The API checks the current profile version/reuse permission in the same transaction as registration and capacity.","properties":{"acknowledgements":{"type":"object"},"answers":{"type":"object"},"approveSharing":{"const":true,"type":"boolean"},"child":{"type":"object"},"consent":{"const":true,"type":"boolean"},"consentStatements":{"items":{"properties":{"key":{"type":"string"},"label":{"type":"string"},"text":{"type":"string"}},"required":["key","label","text"],"type":"object"},"type":"array"},"emergencyContact":{"type":"object"},"guardian":{"type":"object"},"profile":{"properties":{"id":{"format":"uuid","type":"string"},"version":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["id","version"],"type":"object"},"secondaryGuardian":{"type":"object"},"sessionIds":{"items":{"format":"uuid","type":"string"},"maxItems":6,"type":"array"},"source":{"type":"object"}},"required":["profile","approveSharing","consentStatements","child","guardian","consent"],"type":"object"}}},"required":true},"responses":{"201":{"description":"Registration receipt in data.registration; approved disclosure persisted atomically"},"400":{"description":"Invalid reference or missing approval"},"401":{"description":"Sign-in required"},"403":{"description":"Account credential required; The role may not book (book; a caregiver)"},"404":{"description":"Camp or owned child profile not found"},"409":{"description":"Profile changed, reuse revoked, form closed, or consent wording changed; reload and review"},"422":{"description":"Registration answers or selected sessions are invalid"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"Submit an explicitly approved registration using your child profile","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Registration receipts belong to the submitting adult.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/registrations/$ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'","title":"Submit an explicitly approved registration using your child profile"}],"x-internal":true}},"/api/users/viewer/registrations/{registrationId}/answers":{"get":{"description":"The questions the camp's current registration form requires that one of the viewer's own registrations (one they submitted, not cancelled) has not answered: visible required canonical attendee fields and the camp's own questions. Acknowledgements, consent, sessions and the how-you-heard question are never asked again here. Home's answer_form action opens it.","parameters":[{"description":"The registration (the answer_form Home action's registrationId)","in":"path","name":"registrationId","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"The registration and its missing questions.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyRegistrationQuestions"}}},"example":{"success":true,"data":{"registration":{"id":"5b1f0c7e-2d4a-4e8b-9f3c-1a2b3c4d5e6f","organizationId":"8c2d4e6f-1a3b-4c5d-8e7f-9a0b1c2d3e4f","organizationName":"Lakeside Day Camp","confirmationCode":"SO-7KQ2MX","childProfileId":"3e5a7c9b-2d4f-4a6c-8e0b-1d3f5a7c9e2b","status":"enrolled"},"formVersion":"2026-10-28T17:04:11.482915+00:00","questions":[{"key":"child.allergies","label":"Allergies, medication, or medical needs","hint":"Anything staff must know to keep your camper safe.","control":"textarea","options":[]},{"key":"answers.tshirt","label":"T-shirt size","hint":"","control":"choice","options":["Youth S","Youth M","Youth L"]}]}}}}},"400":{"description":"Registration id must be a UUID"},"401":{"description":"Sign-in required"},"403":{"description":"Account credential required; The role may not book (book; a caregiver)"},"404":{"description":"Not one of your registrations, or it was cancelled"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"Read a registration's missing questions","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. A registration belongs to the adult who submitted it.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/registrations/$REGISTRATION_ID/answers\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read a registration's missing questions"}],"x-internal":true},"post":{"description":"Adds the parent's answers to questions the registration is missing, exactly as sent, validated as the public form validates them. Only questions the GET lists may be answered, and only while the camp's form is the formVersion read; a question answered since or no longer asked, or a form saved since, is 409 and nothing is written. The answers are written in the camp-correction lock order (camp, then registration), recorded in the registration's history as a correction by the parent, added to the registration's sharing record (sharedFields and fieldLabels), and emailed to the camp's registration notification address when it has one. Answer some or all of the questions; the response lists what is still missing.","parameters":[{"description":"The registration (the answer_form Home action's registrationId)","in":"path","name":"registrationId","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["formVersion","approveSharing","values"],"properties":{"formVersion":{"type":"string","description":"The formVersion the questions were read with."},"approveSharing":{"const":true,"type":"boolean","description":"The parent approved sending these answers to the camp."},"values":{"type":"object","minProperties":1,"description":"Answers keyed by question key: a string, or for multi-choice a list of the offered choices.","additionalProperties":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}}}}}}},"responses":{"200":{"description":"Answers saved; the registration and the questions still missing.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyRegistrationQuestions"}}},"example":{"success":true,"data":{"registration":{"id":"5b1f0c7e-2d4a-4e8b-9f3c-1a2b3c4d5e6f","organizationId":"8c2d4e6f-1a3b-4c5d-8e7f-9a0b1c2d3e4f","organizationName":"Lakeside Day Camp","confirmationCode":"SO-7KQ2MX","childProfileId":"3e5a7c9b-2d4f-4a6c-8e0b-1d3f5a7c9e2b","status":"enrolled"},"formVersion":"2026-10-28T17:04:11.482915+00:00","questions":[]}}}}},"400":{"description":"Missing approval or answers, or an answer the question does not accept"},"401":{"description":"Sign-in required"},"403":{"description":"Account credential required; The role may not book (book; a caregiver)"},"404":{"description":"Not one of your registrations, or it was cancelled"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"409":{"description":"The camp's form changed, or a question was answered since or is no longer asked; reload and review"}},"security":[{"AccountCredential":[]}],"summary":"Answer a registration's missing questions","tags":["Internal family profiles"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. A registration belongs to the adult who submitted it.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/registrations/$REGISTRATION_ID/answers\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"formVersion\":\"2026-10-28T17:04:11.482915+00:00\",\"approveSharing\":true,\"values\":{\"child.allergies\":\"Peanuts\"}}'","title":"Answer a registration's missing questions"}],"x-internal":true}},"/api/users/viewer/payment-method":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Get the saved cards","description":"Returns the default and backup cards, the billing address and the receipt email, refreshed from Stripe. After setup, a card newer than both becomes the default and the previous default fills an empty backup slot. `paymentMethod` repeats the default card for older apps.","responses":{"200":{"description":"The saved cards.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyPaymentMethods"}}},"example":{"success":true,"data":{"paymentMethod":{"brand":"visa","last4":"4242"},"default":{"id":"pm_1PqDefault","brand":"visa","last4":"4242","expMonth":4,"expYear":2031,"name":"Sam Rivera"},"backup":null,"cards":[{"id":"pm_1PqDefault","brand":"visa","last4":"4242","expMonth":4,"expYear":2031,"name":"Sam Rivera"}],"billingAddress":{"line1":"12 Lake St","city":"Portland","region":"OR","postalCode":"97201"},"receiptEmail":null}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"403":{"description":"Only the family's owner manages cards, top-ups and gift cards (manage_cards)"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Payments aren't configured on this server (no Stripe key, `STRIPE_SECRET_KEY`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Payments aren't configured on this server.","data":null}}}}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Get the saved card","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/payment-method\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"patch":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Choose cards, billing address and receipt email","description":"Chooses the default and backup among the parent’s own saved cards (checked with Stripe), saves the billing address on Stripe and here, and sets the receipt email. `makeDefault` applies first; making the backup the default swaps the two. `backupId: null` clears the backup. `receiptEmail` needs no saved card (a family that pays only with credits gets receipts too); `receiptEmail: null` sends receipts to the account email again. Send at least one property.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"backupId":{"type":["string","null"],"pattern":"^pm_"},"makeDefault":{"type":"string","pattern":"^pm_"},"billingAddress":{"$ref":"#/components/schemas/FamilyPaymentMethodBillingAddress"},"receiptEmail":{"type":["string","null"],"format":"email","maxLength":254,"description":"Where payment receipts are emailed instead of the account email; null clears it."}}}}}},"responses":{"200":{"description":"The saved cards after the change.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyPaymentMethods"}}}}}},"400":{"description":"Unknown property, no property, a malformed card id, an address without a 2-letter region and 5-digit postal code, or a receiptEmail that is not one email address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"billingAddress needs a 2-letter region and a 5-digit postal code.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"409":{"description":"No saved card yet, for a change of cards or billing address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Add a card first.","data":null}}}},"422":{"description":"A card that is not the parent’s, or a backup equal to the default. Also a card Stripe does not know, or another family’s card.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"That card is not one of your saved cards.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Stripe did not save the address. Also when Stripe can’t be reached (or has no key locally) to look up a card in neither slot: “Card setup is unavailable. Try again shortly.” Also on a server without a Stripe key (`STRIPE_SECRET_KEY`), before anything is read or recorded: “Payments aren't configured on this server.”","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"The billing address could not be saved. Try again.","data":null}}}},"403":{"description":"Only the family's owner manages cards, top-ups and gift cards (manage_cards)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Choose cards, billing address and receipt email","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/payment-method\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"makeDefault\": \"pm_1PqBackup\", \"billingAddress\": {\"line1\": \"12 Lake St\", \"city\": \"Portland\", \"region\": \"OR\", \"postalCode\": \"97201\"}}'"}]}},"/api/users/viewer/payment-method/setup":{"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Start card setup","description":"Returns a Stripe Checkout URL in setup mode. Send the browser there; Stripe returns the parent to `returnUrl` afterward.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["returnUrl"],"properties":{"returnUrl":{"type":"string","format":"uri","description":"Absolute URL whose origin is in the API’s `REDIRECT_ALLOWLIST`."}}},"example":{"returnUrl":"https://data.schoolsoutapp.com/family/bookings/new?session=c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48"}}}},"responses":{"200":{"description":"The Stripe-hosted setup URL.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}}}},"example":{"success":true,"data":{"url":"https://checkout.stripe.com/c/pay/cs_live_..."}}}}},"400":{"description":"The body isn’t exactly `{ returnUrl }`, or its origin isn’t allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"returnUrl must be an allowed absolute URL.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Stripe couldn’t start setup. Nothing was saved; try again. Also on a server without a Stripe key (`STRIPE_SECRET_KEY`), before anything is read or recorded: “Payments aren't configured on this server.”","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Card setup is unavailable. Try again shortly.","data":null}}}},"403":{"description":"Only the family's owner manages cards, top-ups and gift cards (manage_cards)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Start card setup","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/payment-method/setup\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"returnUrl\": \"https://data.schoolsoutapp.com/family/bookings/new?session=c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48\"}'"}]}},"/api/users/viewer/payment-method/setup-intent":{"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Start card setup in the page","description":"Returns the client secret of a Stripe SetupIntent for an off-session card on the parent’s Stripe customer, for Stripe Elements to confirm in the page (`stripe.confirmCardSetup`, 3-D Secure included). The publishable key is the site’s own setting. Sending it again reuses the open SetupIntent. Once Stripe confirms, read `GET /api/users/viewer/payment-method`: it saves the new card as the default, or with `makeDefault: false` as the backup beside an existing default. The hosted `setup` route stays for a redirect.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"makeDefault":{"type":"boolean","default":true,"description":"Whether the new card becomes the default; false makes it the backup when the parent has a default card."}}},"example":{"makeDefault":true}}}},"responses":{"200":{"description":"The SetupIntent’s client secret.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["clientSecret"],"properties":{"clientSecret":{"type":"string","description":"Pass to Stripe Elements; never log or store it."}}}}},"example":{"success":true,"data":{"clientSecret":"seti_1Q2w3E4r5T6y7U8i_secret_Op9As8Df7Gh6"}}}}},"400":{"description":"The body has a property other than `makeDefault`, or it isn’t a boolean.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"The request body has an unknown or mistyped property.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Stripe couldn’t start setup. Nothing was saved; try again. Also on a server without a Stripe key (`STRIPE_SECRET_KEY`), before anything is read or recorded: “Payments aren't configured on this server.”","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Card setup is unavailable. Try again shortly.","data":null}}}},"403":{"description":"Only the family's owner manages cards, top-ups and gift cards (manage_cards)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Start card setup in the page","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/payment-method/setup-intent\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"makeDefault\": true}'"}]}},"/api/users/viewer/booking-policy":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Get the booking policy","description":"Returns the money policy bookings use: the service fee per child and the group rate per family. Show it before the parent approves; each booking then reports the fee it carries (`payment.serviceFeeCents`).","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyBookingPolicy"}}},"example":{"success":true,"data":{"serviceFee":{"perChild":{"amountCents":500,"currency":"USD"}},"groupRate":{"perFamily":{"amountCents":500,"currency":"USD"},"families":2}}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"$ref":"#/components/responses/BookingsDisabled"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Get the booking policy","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/booking-policy\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/booking-quote":{"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Quote a booking","description":"Returns what booking a session for each child, in checkout order, would cost and what would stop it, without booking or holding anything. The spending limit is `maxSpend` when sent, else the session's listed price (most catalog sessions state no price basis, so the checkout sends the price it shows); the service fee is the policy's; `useCreditsCents` (capped by the wallet) is applied to the children in order until it runs out, each at most its limit plus fee; the card hold is what is left, raised to Stripe's minimum when small. These are the amounts the approvals then hold: send one approval per child with the child's `money.maxSpend`, `childProfileVersion` and `money.creditsApplied`. A POST because it takes a list of children; it writes nothing.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyBookingQuoteInput"}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["quote"],"properties":{"quote":{"$ref":"#/components/schemas/FamilyBookingQuote"}}}}},"example":{"success":true,"data":{"quote":{"sessionId":"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48","children":[{"childProfileId":"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93","childProfileVersion":4,"eligibility":{"alreadyBooked":false,"reuseConsentMissing":false,"missingFields":[]},"wouldWaitlist":false,"money":{"maxSpend":{"amountCents":42500,"currency":"USD"},"serviceFee":{"amountCents":500,"currency":"USD"},"creditsApplied":{"amountCents":30000,"currency":"USD"},"cardHold":{"amountCents":13000,"currency":"USD"},"stripeMinimumApplied":false}},{"childProfileId":"5b2e8d14-3c7a-4f91-b6d0-9e4a1c7f2b58","childProfileVersion":1,"eligibility":{"alreadyBooked":false,"reuseConsentMissing":true,"missingFields":["child.lastName"]},"wouldWaitlist":true,"money":{"maxSpend":{"amountCents":42500,"currency":"USD"},"serviceFee":{"amountCents":500,"currency":"USD"},"creditsApplied":{"amountCents":0,"currency":"USD"},"cardHold":{"amountCents":43000,"currency":"USD"},"stripeMinimumApplied":false}}],"credits":{"available":{"amountCents":30000,"currency":"USD"},"requested":{"amountCents":50000,"currency":"USD"},"applied":{"amountCents":30000,"currency":"USD"}},"paymentMethod":{"choice":"default","saved":true},"totals":{"maxSpend":{"amountCents":85000,"currency":"USD"},"serviceFees":{"amountCents":1000,"currency":"USD"},"credits":{"amountCents":30000,"currency":"USD"},"cardHolds":{"amountCents":56000,"currency":"USD"}}}}}}}},"400":{"description":"An unknown property, a `sessionId` or child that is not a UUID, no children, too many or repeated children, a `maxSpend` an approval would refuse (including another currency than the session's listed price), `useCreditsCents` below 1, or a `paymentMethod` other than `default` or `backup`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"childProfileIds must list 1 to 20 children.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"description":"Bookings are off, a child profile isn't available to this parent, or the session is no longer listed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"That session is no longer available.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not book (book; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Quote a booking","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/booking-quote\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sessionId\": \"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48\", \"childProfileIds\": [\"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93\", \"5b2e8d14-3c7a-4f91-b6d0-9e4a1c7f2b58\"], \"maxSpend\": {\"amountCents\": 42500, \"currency\": \"USD\"}, \"useCreditsCents\": 50000, \"paymentMethod\": \"default\"}'"}]}},"/api/users/viewer/bookings":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"List bookings","description":"Returns the parent’s bookings, newest first, a page at a time (`nextCursor`). Filters: `childId`, `from`/`to` (the session’s date range overlaps them; bookings without dates are left out when either is set), and `phase`. Event history is omitted; get a single booking to read it.","responses":{"200":{"description":"The parent’s bookings.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["bookings","nextCursor"],"properties":{"bookings":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/FamilyBooking"},{"$ref":"#/components/schemas/FamilyBookingSchedule"}]}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` for the next page; `null` on the last page."}}}}},"example":{"success":true,"data":{"bookings":[{"id":"6f1d2c3b-4a5e-4f60-8b7a-1c2d3e4f5a6b","phase":"booked","status":"confirmed","paymentStatus":"captured","childProfileId":"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93","organization":{"id":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Lakeside Science Camp"},"activity":{"id":"7d3b9f60-2a4c-4e8b-9f1d-6c5a0e2b8d17","name":"Summer Science Camp"},"session":{"id":"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48","name":"Robotics Week","startDate":"2027-07-12","endDate":"2027-07-16","startTime":"09:00","endTime":"15:00","dates":["2027-07-12","2027-07-13","2027-07-14","2027-07-15","2027-07-16"],"dayTimes":[]},"approval":{"approvedAt":"2026-10-09T17:00:00Z","maxSpend":{"amountCents":45000,"currency":"USD"},"allowWaitlist":true,"autoAcceptDecisions":false,"sharedFields":["child.birthDate"]},"progressStage":null,"decision":null,"decisions":[],"receipt":{"providerReference":"LSC-20931","providerStatus":"enrolled","amount":{"amountCents":42500,"currency":"USD"},"confirmedAt":"2026-10-09T17:06:35Z","source":"hallpass"},"failure":null,"checkingWith":null,"pollAfterMs":null,"createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T17:06:41Z","waitlist":null,"canceledBy":null,"refund":null,"dueOnProviderSite":null,"note":null,"askAgainAfter":null,"replacesBookingId":null,"replacedByBookingId":null,"payment":{"paymentMethod":"default","creditsCents":0,"cardHoldCents":0,"serviceFeeCents":500},"cancellation":{"allowed":false,"action":"cancel","reason":"booked","deadline":null,"refundableCents":0,"methods":[]},"bookedBy":null,"hold":null}],"nextCursor":null}}}}},"400":{"description":"A filter, limit or cursor is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"from must be a date (YYYY-MM-DD).","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"$ref":"#/components/responses/BookingsDisabled"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"No access to the family's bookings"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"List bookings","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/bookings\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"childId","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Only this child’s bookings."},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"Sessions ending on or after this date."},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"Sessions starting on or before this date."},{"name":"phase","in":"query","required":false,"schema":{"$ref":"#/components/schemas/BookingPhase"},"description":"Only bookings in this phase."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, at most 100."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"`nextCursor` from the previous page."}]},"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Approve a booking","description":"Records the parent’s approval, places a hold on their card for the spending limit plus the service fee (less any credits), and sends the booking to Hallpass. A fast checkout can finish within this request; otherwise, follow the booking until it settles. The child profile must allow reuse, as it does for registration: send `reuseAllowed: true` with the approval to record that consent on the child. `profileFields` saves new details to the child's profile first. The camp's guardian is the adult approving (the owner or a co-parent): their account's name, email and contact phone, never the child's profile.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingApprovalInput"}}}},"responses":{"200":{"description":"The existing booking for this `idempotencyKey`, returned because the request was a replay.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["booking"],"properties":{"booking":{"$ref":"#/components/schemas/FamilyBookingDetail"}}}}}}}},"201":{"description":"The new booking. Its status may already be past `authorizing`.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["booking"],"properties":{"booking":{"$ref":"#/components/schemas/FamilyBookingDetail"}}}}},"example":{"success":true,"data":{"booking":{"id":"6f1d2c3b-4a5e-4f60-8b7a-1c2d3e4f5a6b","phase":"in_progress","status":"accepted","paymentStatus":"held","childProfileId":"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93","organization":{"id":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Lakeside Science Camp"},"activity":{"id":"7d3b9f60-2a4c-4e8b-9f1d-6c5a0e2b8d17","name":"Summer Science Camp"},"session":{"id":"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48","name":"Robotics Week","startDate":"2027-07-12","endDate":"2027-07-16","startTime":"09:00","endTime":"15:00","dates":["2027-07-12","2027-07-13","2027-07-14","2027-07-15","2027-07-16"],"dayTimes":[]},"approval":{"approvedAt":"2026-10-09T17:00:00Z","maxSpend":{"amountCents":45000,"currency":"USD"},"allowWaitlist":true,"autoAcceptDecisions":false,"sharedFields":["child.birthDate"]},"progressStage":null,"decision":null,"decisions":[],"receipt":null,"failure":null,"checkingWith":null,"pollAfterMs":2000,"createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T17:06:41Z","events":[{"kind":"approval_granted","status":"authorizing","actor":"parent","summary":"Approved the booking with a spending limit of 450.00 USD.","createdAt":"2026-10-09T17:00:00Z"},{"kind":"payment_hold_placed","status":"prepared","actor":"system","summary":"Held 450.00 USD on the family card.","createdAt":"2026-10-09T17:00:01Z"},{"kind":"task_submitted","status":"submitting","actor":"system","summary":"Sent the approved booking to Hallpass.","createdAt":"2026-10-09T17:00:01Z"}],"waitlist":null,"canceledBy":null,"refund":null,"dueOnProviderSite":null,"note":null,"askAgainAfter":null,"replacesBookingId":null,"replacedByBookingId":null,"payment":{"paymentMethod":"default","creditsCents":0,"cardHoldCents":45500,"serviceFeeCents":500},"cancellation":{"allowed":true,"action":"cancel","reason":null,"deadline":null,"refundableCents":0,"methods":[]},"bookedBy":null,"hold":null}}}}}},"400":{"description":"The approval is invalid: an unknown property, `approveBooking` not `true`, a spending limit or currency that isn’t allowed, a field that can’t be shared or is missing from the profile, or too many answers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Approve the booking to continue.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"403":{"description":"`reuseAllowed` from an adult who is not the child profile's owner: only the owner grants registration reuse; The role may not book (book; a caregiver)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Only the profile owner can change registration reuse permission.","data":null}}}},"404":{"description":"Bookings are off, the child profile isn’t available to this parent, or the session is no longer listed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"That session is no longer available.","data":null}}}},"409":{"description":"The profile changed or no longer allows reuse, the `idempotencyKey` was reused with different details, the child already has an active or confirmed booking for this session, the wallet does not cover `useCreditsCents`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"This child already has a booking in progress or confirmed for this session.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Approve a booking","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/bookings\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"idempotencyKey\": \"0b7c3f52-9a41-4e2d-8f6a-3c1d5e7b9a20\", \"childProfileId\": \"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93\", \"childProfileVersion\": 4, \"sessionId\": \"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48\", \"maxSpend\": {\"amountCents\": 45000, \"currency\": \"USD\"}, \"allowWaitlist\": true, \"autoAcceptDecisions\": false, \"shareFields\": [\"child.birthDate\"], \"answers\": [{\"key\": \"photo_consent\", \"label\": \"Photo consent\", \"value\": false}], \"approveBooking\": true}'"}]}},"/api/users/viewer/bookings/{bookingId}":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Get a booking","description":"Returns one booking with its full event history. Read it again after `pollAfterMs`. A booking that belongs to another account looks exactly like one that doesn’t exist.","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The booking and its events.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["booking","providerAccount"],"properties":{"booking":{"anyOf":[{"$ref":"#/components/schemas/FamilyBookingDetail"},{"$ref":"#/components/schemas/FamilyBookingSchedule"}]},"providerAccount":{"type":["object","null"],"description":"The provider account the booking carries when Hallpass creates or reuses one for the approving adult (BOOKING_PROVIDER_ACCOUNTS), such as a CourseStorm account; null otherwise. createdAt is null until the run creates it at the provider.","additionalProperties":false,"required":["provider","site","usernameDisplay","origin","createdAt"],"properties":{"provider":{"type":"string"},"site":{"type":"string","format":"uri"},"usernameDisplay":{"type":"string"},"origin":{"type":"string","enum":["attached","created"],"x-open-enum":true},"createdAt":{"type":["string","null"],"format":"date-time"}}}}}}},"example":{"success":true,"data":{"booking":{"id":"6f1d2c3b-4a5e-4f60-8b7a-1c2d3e4f5a6b","phase":"booked","status":"confirmed","paymentStatus":"captured","childProfileId":"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93","organization":{"id":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Lakeside Science Camp"},"activity":{"id":"7d3b9f60-2a4c-4e8b-9f1d-6c5a0e2b8d17","name":"Summer Science Camp"},"session":{"id":"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48","name":"Robotics Week","startDate":"2027-07-12","endDate":"2027-07-16","startTime":"09:00","endTime":"15:00","dates":["2027-07-12","2027-07-13","2027-07-14","2027-07-15","2027-07-16"],"dayTimes":[]},"approval":{"approvedAt":"2026-10-09T17:00:00Z","maxSpend":{"amountCents":45000,"currency":"USD"},"allowWaitlist":true,"autoAcceptDecisions":false,"sharedFields":["child.birthDate"]},"progressStage":null,"decision":null,"decisions":[],"receipt":{"providerReference":"LSC-20931","providerStatus":"enrolled","amount":{"amountCents":42500,"currency":"USD"},"confirmedAt":"2026-10-09T17:06:35Z","source":"hallpass"},"failure":null,"checkingWith":null,"pollAfterMs":null,"createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T17:06:41Z","events":[{"kind":"approval_granted","status":"authorizing","actor":"parent","summary":"Approved the booking with a spending limit of 450.00 USD.","createdAt":"2026-10-09T17:00:00Z"},{"kind":"payment_hold_placed","status":"prepared","actor":"system","summary":"Held 450.00 USD on the family card.","createdAt":"2026-10-09T17:00:01Z"},{"kind":"task_submitted","status":"submitting","actor":"system","summary":"Sent the approved booking to Hallpass.","createdAt":"2026-10-09T17:00:01Z"},{"kind":"confirmation_received","status":"confirmed","actor":"hallpass","summary":"The camp confirmed reference LSC-20931.","createdAt":"2026-10-09T17:06:40Z"},{"kind":"payment_captured","status":"confirmed","actor":"system","summary":"Charged 425.00 USD to the family card.","createdAt":"2026-10-09T17:06:41Z"}],"waitlist":null,"canceledBy":null,"refund":null,"dueOnProviderSite":null,"note":null,"askAgainAfter":null,"replacesBookingId":null,"replacedByBookingId":null,"payment":{"paymentMethod":"default","creditsCents":0,"cardHoldCents":0,"serviceFeeCents":500},"cancellation":{"allowed":false,"action":"cancel","reason":"booked","deadline":null,"refundableCents":0,"methods":[]},"bookedBy":null,"hold":null},"providerAccount":null}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"description":"No such booking for this parent, or bookings are off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Booking not found.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"No access to the family's bookings"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Get a booking","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/bookings/$BOOKING_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/bookings/{bookingId}/decisions/{decisionId}":{"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Answer a camp question","description":"Accepts one of the offered options, or declines. Declining ends the booking and releases the hold.","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"decisionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["outcome"],"properties":{"outcome":{"type":"string","enum":["accept","decline"]},"optionId":{"type":"string","description":"Required to accept; must be absent to decline."},"answer":{"type":["string","null"],"minLength":1,"maxLength":2000,"description":"Your typed answer. Required to accept an `answer_required` decision; omit it otherwise."}}},"examples":{"accept":{"value":{"outcome":"accept","optionId":"medium"}},"decline":{"value":{"outcome":"decline"}}}}}},"responses":{"200":{"description":"The answer was recorded and checkout resumes.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["booking"],"properties":{"booking":{"$ref":"#/components/schemas/FamilyBookingDetail"}}}}}}}},"400":{"description":"`optionId` is missing when accepting, present when declining, or not one of the offered options, or Hallpass refused a typed answer that repeats an attached detail or holds a token, card number or email address (the message says which; fix the answer and send it again). A question that asks for a secret detail can't be answered here.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Choose one of the offered options.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"$ref":"#/components/responses/BookingNotFound"},"409":{"description":"The question was already answered or has expired, or the booking is briefly busy. Read the booking again instead of retrying.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"That decision is no longer waiting for an answer.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Hallpass couldn’t be reached. Nothing was recorded; try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"The answer could not be sent. Try again.","data":null}}}},"403":{"description":"The role may not book (book; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Answer a camp question","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/bookings/$BOOKING_ID/decisions/$DECISION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"outcome\": \"accept\", \"optionId\": \"medium\"}'"}]}},"/api/users/viewer/bookings/{bookingId}/cancel":{"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Cancel a booking","description":"Cancels the booking and releases the hold, as long as the camp’s checkout hasn’t started payment. An application still waiting for the camp’s answer (`phase: applied`) is withdrawn the same way: it ends cancelled by the parent, with nothing charged. The body is optional: `reason` is recorded. When the camp cancelled (`canceledBy: provider`), the same route records how the parent wants their payment back: send only `refundMethod` (`card` by default). `credits` returns everything to the wallet at once; `card` refunds the card part to the card (`refund.status` stays `pending` until Stripe confirms) and any credits part to the wallet. The amount is always what the family paid. Every booking's `cancellation` says beforehand what this route does with it (`action`), whether it accepts it now (`allowed`, else `reason`), and what it refunds. Cancelling again while a cancel is on its way (`cancellation.reason: cancel_requested`) returns the booking as it is.","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The cancellation was recorded. The status becomes `cancelled` once Hallpass confirms.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["booking"],"properties":{"booking":{"$ref":"#/components/schemas/FamilyBookingDetail"}}}}}}}},"400":{"description":"An unknown property, `refundMethod` or `reason`, or a `reason` for a booking the camp cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"refundMethod must be credits or card.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"$ref":"#/components/responses/BookingNotFound"},"409":{"description":"The booking’s `cancellation` is not allowed (already final, checkout has started payment, being checked with the camp, a refund was already chosen, or nothing was paid), or the booking is briefly busy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"A refund was already chosen for this booking.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not book (book; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Cancel a booking","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/bookings/$BOOKING_ID/cancel\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"reason\": \"plans_changed\"}'"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyBookingCancelInput"}}}}}},"/api/activities/scores":{"get":{"description":"Returns score summaries for up to 24 distinct canonical activities from ?ids=UUID,UUID with scores, missing, scoringVersion, and tier. The default and cached=false calculate live scores. Optional cached=true returns persisted summaries without evidence reads or calculation; scores and missing follow request order, and missing caches never trigger live fallback. Cached reads exclude deleted and blacklisted records even for moderation readers and deny all previews. Both modes retain Starter+ access. cached accepts one exact true or false value; malformed or repeated values return 400.","parameters":[{"description":"Comma-separated distinct record UUIDs, up to 24. UUID case is ignored.","in":"query","name":"ids","required":true,"schema":{"type":"string"}},{"description":"One exact true or false value. true reads persisted summaries; omitted or false calculates live summaries.","in":"query","name":"cached","required":false,"schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"missing":{"items":{"format":"uuid","type":"string"},"type":"array"},"scores":{"items":{"additionalProperties":true,"type":"object"},"type":"array"},"scoringVersion":{"type":"string"},"tier":{"type":"string"}},"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Score summaries and IDs with no available result. Cached mode never recalculates missing scores."},"400":{"description":"Missing, invalid, or excessive IDs, or malformed/repeated cached values."},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"description":"Cached score summaries are unavailable in preview."},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Read activity score summaries","tags":["Organization catalog"],"x-auth":"Starter+ account cookie, account header, or permitted machine key. Cached reads deny all previews.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/activities/scores\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"ids=$ACTIVITY_ID\" \\\n  --data-urlencode \"cached=true\"","title":"Read activity cached summaries"}]}},"/api/activities/{id}/provenance":{"get":{"description":"Returns data.activity with id and visible bookkeeping moved from normal reads. Only authorized ADMIN readers additionally receive field_provenance, source_records, merge_jobs, and relationship_jobs. Nonblank configuration_id is retained as record identity metadata for feedback targeting and omitted from display column metadata. Moved fields are external_ids, created_at, updated_at, source_created_at, field_provenance, source_records, merge_jobs, and relationship_jobs. Field provenance separates current value ownership (state, sources, value_state) from source checks and field decisions. Entries cover visible fields before sparse projection, including cleared and unknown values. A found or checked_not_found observation can exist without a value change; absence of observation means no recorded check. Sources, latest_observation, and history share a flat record shape; history is newest first. The latest observation follows observed time, while decision history follows contribution time. Omitted field_provenance means unavailable; an absent entry in an available map is unknown. Field citations are distinct from site-scoped scrape URLs.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"name":"includeResolution","in":"query","required":false,"description":"Include field_resolution on each returned entity row: own, inherited, or unset for each projected field. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeSources","in":"query","required":false,"description":"Include field_sources on each returned entity row, mapping fields to the canonical entity supplying their effective value or configuration unit. Independent of includeResolution; defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeOwnValues","in":"query","required":false,"description":"Include own_values on each returned entity row, retaining null local values. Requires editor authorization; otherwise returns 403. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"activity":{"$ref":"#/components/schemas/CanonicalProvenanceRow"}},"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Canonical activity bookkeeping and authorized history"},"403":{"description":"Provenance is unavailable in preview"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Read activity bookkeeping and provenance","tags":["Organization catalog"],"x-auth":"Account cookie or header, or permitted machine key, from Starter up. Bookkeeping follows the existing field masks. Field provenance and raw histories require an ADMIN user-backed viewer, including an ADMIN-owned machine key; staff, bare service credentials, and membership upgrades do not grant this authority. All previews are denied.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/activities/$ACTIVITY_ID/provenance\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Read permitted activity bookkeeping and provenance"}]}},"/api/activities/{id}/score":{"get":{"description":"Returns one canonical activity with its School’s Out activity score as pure JSON: the assembled ActivityScoreInput (the program's fields, sources, continuity signals, and its camp's score and safety cap), the full score with criteria, indicators, sources, caps, flags, and field ledger, and the scoring version. The live activity rubric uses the selected active cover and has no enrollment-pricing criterion. This endpoint calculates the live score and returns its input/evidence separately from normal catalog reads. The embedded canonical record follows the sparse public projection.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"additionalProperties":true,"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Live score, scoring input, canonical record, scoringVersion, tier, and lockedColumns. Organization responses also include moderation and optional peers."},"400":{"description":"Invalid record UUID."},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Read one live activity score","tags":["Organization catalog"],"x-auth":"Existing Starter+ score access and field masks apply. Active organization membership retains its scoped detail access.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/activities/$ACTIVITY_ID/score\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Read activity live score detail"}]}},"/api/organizations/scores":{"get":{"description":"Returns score summaries for up to 24 distinct canonical organizations from ?ids=UUID,UUID with scores, missing, scoringVersion, and tier. The default and cached=false calculate live scores. Optional cached=true returns persisted summaries without evidence reads or calculation; scores and missing follow request order, and missing caches never trigger live fallback. Organization caches from an older scoring version remain missing until the updated worker refreshes them. Cached reads exclude deleted and blacklisted records even for moderation readers and deny all previews. Both modes retain Starter+ access. cached accepts one exact true or false value; malformed or repeated values return 400.","parameters":[{"description":"Comma-separated distinct record UUIDs, up to 24. UUID case is ignored.","in":"query","name":"ids","required":true,"schema":{"type":"string"}},{"description":"One exact true or false value. true reads persisted summaries; omitted or false calculates live summaries.","in":"query","name":"cached","required":false,"schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"missing":{"items":{"format":"uuid","type":"string"},"type":"array"},"scores":{"items":{"additionalProperties":true,"type":"object"},"type":"array"},"scoringVersion":{"type":"string"},"tier":{"type":"string"}},"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Score summaries and IDs with no available result. Cached mode never recalculates missing scores."},"400":{"description":"Missing, invalid, or excessive IDs, or malformed/repeated cached values."},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"description":"Cached score summaries are unavailable in preview."},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Read organization score summaries","tags":["Organization catalog"],"x-auth":"Starter+ account cookie, account header, or permitted machine key. Cached reads deny all previews.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/organizations/scores\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"ids=$ORGANIZATION_ID\" \\\n  --data-urlencode \"cached=true\"","title":"Read organization cached summaries"}]}},"/api/organizations/{id}/provenance/timeline":{"get":{"description":"Returns settled canonical contributions to a camp and its currently associated activities and sessions, including sessions associated through an activity. Supply entityKind and entityId together to restrict the page to that exact currently associated organization, activity, or session; omit both for camp-wide history. Scope is applied before pagination, and cursors cannot cross scopes. Entries use accepted merge masks or retained set/clear/inherit decisions, never scraper completion, pending or failed jobs, or observation-only checks; same-value confirmations may appear. The page and ADMIN-only value reconstruction share one repeatable-read, read-only snapshot. For each entity on the page, ADMIN before/after values are reconstructed from its current logical projection and full retained per-entity applied history, including contributions outside the requested page, using pipeline contribution precedence and reversal semantics, including URL additions and removals. Each recovered before value becomes the next older contribution's after value; reversed contributions remain marked reversed. Staff receive the same source/entity/time/field summary but restricted value sides and incur no reconstruction. Storage-only bookkeeping fields remain restricted even for ADMIN. This is reconstructed retained history, not immutable snapshots, current field ownership, or a complete audit. The oldest before value, ambiguous legacy URL invalidations, and genuine history gaps are unavailable; gaps are not filled from unaccepted source data. Media sides are reconstructed read-only through the exact-entity media projection and retained contribution/removal history. An available media value is an array of sanitized ProvenanceTimelineMediaItem objects without source attribution; [] means known empty. Reconstruction excludes uploads created after the contribution and non-displayable assets. If a formerly nonempty state has no displayable members, the side is unavailable rather than a false empty. Same-entity uploads use the protected organization asset URL; remote media display URLs preserve signed query strings and fragments. Deleted jobs, former child associations, older writes, and erased legacy reversal masks may leave gaps. `coverage.values` is `reconstructed_from_history`. Available sides contain canonical logical JSON, including explicit null; unavailable or restricted sides carry no value. Raw inputs, reasons, evidence text, and personal attribution are omitted. Timeline source-website queries and fragments are removed; media display URLs retain signed queries and fragments. Entity names retain existing prose masking, and legacy field/URL masks use pipeline compatibility. Unsupported no-field history is skipped with bounded refill; an empty page can have a non-null nextCursor when the scan budget is reached. Continue until nextCursor is null. Deploy/restart the API to enable reconstruction; no worker-write path or schema migration is needed. Every entry also includes organization_score for the parent organization at the inclusive recorded_at timestamp, including activity/session entries. ADMIN reads reconstruct retained accepted facts and evaluate the current rubric at that historical time in the same read-only snapshot. Tied timestamps share an organization state. Reconstructed scores include confidence, interval, caps and explicit history coverage; later facts and evidence are excluded. Missing history, current child associations, unavailable regional pricing and duplicate-review inputs limit reconstruction. No usable historical score facts yields unavailable, never a fabricated zero. Staff receive only state: restricted. Rubric changes may change historical scores; no cache or regional aggregate is written.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"entityKind","description":"Exact record kind to include. Supply once together with entityId; omit both for camp-wide history.","schema":{"type":"string","enum":["organization","activity","session"]}},{"in":"query","name":"entityId","description":"Exact record UUID of entityKind, currently associated with this organization. Supply once together with entityKind. An unavailable or unrelated record returns 404.","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"limit","description":"Maximum entries on this page.","schema":{"type":"integer","minimum":1,"maximum":100,"default":30}},{"in":"query","name":"cursor","description":"Opaque nextCursor from the previous page for this same organization and entity scope. Reusing a cursor with a different scope returns 400. Ordered by merge completion timestamp, entity kind, and job ID descending, preserving submillisecond precision.","schema":{"type":"string"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Sanitized retained contribution history. ADMIN receives reconstructed field values and organization scores where historical inputs permit; staff receives restricted values and scores without reconstruction.","content":{"application/json":{"schema":{"type":"object","required":["data","success"],"properties":{"success":{"const":true},"data":{"type":"object","required":["organizationId","entries","nextCursor","coverage"],"properties":{"organizationId":{"type":"string","format":"uuid"},"entries":{"type":"array","items":{"type":"object","required":["id","recorded_at","entity","source","fields","state"],"properties":{"id":{"type":"string","description":"Stable entity-kind and merge-job identity."},"recorded_at":{"type":"string","format":"date-time","description":"Canonical merge completion time."},"entity":{"type":"object","required":["kind","id","name","deleted"],"properties":{"kind":{"type":"string","enum":["organization","activity","session"]},"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"deleted":{"type":"boolean"}}},"source":{"type":"object","required":["kind","label"],"properties":{"kind":{"type":"string","enum":["scrape","manual_update","overseer","import","other"]},"label":{"type":"string"},"source_url":{"type":"string","format":"uri"},"batch_id":{"type":"string","format":"uuid"}}},"fields":{"type":"array","items":{"type":"object","required":["field","action","before","after"],"properties":{"field":{"type":"string","description":"Canonical field name; media gallery before/after values use arrays of ProvenanceTimelineMediaItem."},"action":{"type":"string","enum":["set","clear","inherit","contributed"],"description":"Legacy masks establish contributed fields without inventing a recorded decision."},"before":{"$ref":"#/components/schemas/ProvenanceTimelineValue"},"after":{"$ref":"#/components/schemas/ProvenanceTimelineValue"}}}},"state":{"type":"string","enum":["applied","reversed"],"description":"Historical contribution state, never a claim of current field ownership."},"organization_score":{"$ref":"#/components/schemas/ProvenanceTimelineOrganizationScore"},"reversed_at":{"type":"string","format":"date-time","description":"Completed batch reversal timestamp when retained; not an individual field restoration time."}}}},"nextCursor":{"type":["string","null"]},"coverage":{"type":"object","required":["history","children","values","reversals"],"properties":{"history":{"const":"retained_canonical_contributions"},"children":{"const":"current_associations"},"values":{"const":"reconstructed_from_history","description":"ADMIN receives reconstructed values where retained history permits; staff receives restricted sides. Storage-only fields remain restricted even for ADMIN."},"reversals":{"const":"legacy_fields_may_be_missing"}}}}}}}}}},"400":{"description":"Invalid organization ID, limit, cursor, or cursor belonging to another organization."},"403":{"description":"Requires a staff or ADMIN account; preview, machine keys, service, and delegated service access are denied."},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Read recorded camp, program, and session updates","tags":["Organization catalog"],"x-auth":"Staff or ADMIN account cookie or account API key. Only ADMIN triggers before/after reconstruction; staff receives restricted value sides. Storage-only bookkeeping remains restricted even for ADMIN. No preview, machine key, service, or delegated service access. This sanitized timeline does not grant the existing ADMIN-only raw provenance authority.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/organizations/$ORGANIZATION_ID/provenance/timeline\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"limit=30\"","title":"Read recorded canonical updates"}],"x-internal":true}},"/api/organizations/{id}/provenance":{"get":{"description":"Returns data.organization with id and visible bookkeeping moved from normal reads. Only authorized ADMIN readers additionally receive field_provenance, source_records, merge_jobs, and relationship_jobs. Nonblank configuration_id is retained as record identity metadata for feedback targeting and omitted from display column metadata. Moved fields are external_ids, created_at, updated_at, source_created_at, field_provenance, source_records, merge_jobs, and relationship_jobs. Field provenance separates current value ownership (state, sources, value_state) from source checks and field decisions. Entries cover visible fields before sparse projection, including cleared and unknown values. A found or checked_not_found observation can exist without a value change; absence of observation means no recorded check. Sources, latest_observation, and history share a flat record shape; history is newest first. The latest observation follows observed time, while decision history follows contribution time. Omitted field_provenance means unavailable; an absent entry in an available map is unknown. Field citations are distinct from site-scoped scrape URLs.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"name":"includeResolution","in":"query","required":false,"description":"Include field_resolution on each returned entity row: own, inherited, or unset for each projected field. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeSources","in":"query","required":false,"description":"Include field_sources on each returned entity row, mapping fields to the canonical entity supplying their effective value or configuration unit. Independent of includeResolution; defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeOwnValues","in":"query","required":false,"description":"Include own_values on each returned entity row, retaining null local values. Requires editor authorization; otherwise returns 403. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"organization":{"$ref":"#/components/schemas/CanonicalProvenanceRow"}},"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Canonical organization bookkeeping and authorized history"},"403":{"description":"Provenance is unavailable in preview"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Read organization bookkeeping and provenance","tags":["Organization catalog"],"x-auth":"Account cookie or header, or permitted machine key, from Starter up. Active organization members receive their existing organization detail access. Bookkeeping follows the existing field masks. Field provenance and raw histories require an ADMIN user-backed viewer, including an ADMIN-owned machine key; staff, bare service credentials, and membership upgrades do not grant this authority. All previews are denied.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/organizations/$ORGANIZATION_ID/provenance\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Read permitted organization bookkeeping and provenance"}]}},"/api/organizations/{id}/score":{"get":{"description":"Returns one canonical organization with its School’s Out organization score as pure JSON: the assembled OrganizationScoreInput, the full OrganizationScore, sources, caps, flags, field ledger, and scoring version. Organization scoring v3 combines accuracy, freshness, completeness, and activity with relative weights 20:20:40:20. Programs/funding, safety, and operator responsiveness are removed, including their input parameters, caps, and flags. Activity uses upcoming sessions, recent activity, and enrollment with weights 25:20:10 normalized by their sum. Any upcoming session earns full upcoming credit. A known session end within the past 365 days inclusive earns full recent credit; missing, invalid, future, and older ends earn none. Registration, continuity, and website reachability no longer contribute Activity indicators; website check age remains a Freshness indicator. Confidence uses collected session and integrity signals alongside source depth, dated fields, and merge confidence; it affects the displayed interval without pulling the overall toward 50. This endpoint calculates the live score and returns its input/evidence separately from normal catalog reads. The embedded canonical record follows the sparse public projection.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"additionalProperties":true,"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Live score, scoring input, canonical record, scoringVersion, tier, and lockedColumns. Organization responses also include moderation and optional peers."},"400":{"description":"Invalid record UUID."},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Read one live organization score","tags":["Organization catalog"],"x-auth":"Existing Starter+ score access and field masks apply. Active organization membership retains its scoped detail access.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/organizations/$ORGANIZATION_ID/score\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Read organization live score detail"}]}},"/api/sessions/scores":{"get":{"description":"Returns score summaries for up to 24 distinct canonical sessions from ?ids=UUID,UUID with scores, missing, scoringVersion, and tier. The default and cached=false calculate live scores. Optional cached=true returns persisted summaries without evidence reads or calculation; scores and missing follow request order, and missing caches never trigger live fallback. Cached reads exclude deleted and blacklisted records even for moderation readers and deny all previews. Both modes retain Starter+ access. cached accepts one exact true or false value; malformed or repeated values return 400.","parameters":[{"description":"Comma-separated distinct record UUIDs, up to 24. UUID case is ignored.","in":"query","name":"ids","required":true,"schema":{"type":"string"}},{"description":"One exact true or false value. true reads persisted summaries; omitted or false calculates live summaries.","in":"query","name":"cached","required":false,"schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"missing":{"items":{"format":"uuid","type":"string"},"type":"array"},"scores":{"items":{"additionalProperties":true,"type":"object"},"type":"array"},"scoringVersion":{"type":"string"},"tier":{"type":"string"}},"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Score summaries and IDs with no available result. Cached mode never recalculates missing scores."},"400":{"description":"Missing, invalid, or excessive IDs, or malformed/repeated cached values."},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"description":"Cached score summaries are unavailable in preview."},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Read session score summaries","tags":["Organization catalog"],"x-auth":"Starter+ account cookie, account header, or permitted machine key. Cached reads deny all previews.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/sessions/scores\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"ids=$SESSION_ID\" \\\n  --data-urlencode \"cached=true\"","title":"Read session cached summaries"}]}},"/api/sessions/{id}/provenance":{"get":{"description":"Returns data.session with id and visible bookkeeping moved from normal reads. Only authorized ADMIN readers additionally receive field_provenance, source_records, merge_jobs, and relationship_jobs. Nonblank configuration_id is retained as record identity metadata for feedback targeting and omitted from display column metadata. Moved fields are external_ids, created_at, updated_at, source_created_at, field_provenance, source_records, merge_jobs, and relationship_jobs. Field provenance separates current value ownership (state, sources, value_state) from source checks and field decisions. Entries cover visible fields before sparse projection, including cleared and unknown values. A found or checked_not_found observation can exist without a value change; absence of observation means no recorded check. Sources, latest_observation, and history share a flat record shape; history is newest first. The latest observation follows observed time, while decision history follows contribution time. Omitted field_provenance means unavailable; an absent entry in an available map is unknown. Field citations are distinct from site-scoped scrape URLs.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"name":"includeResolution","in":"query","required":false,"description":"Include field_resolution on each returned entity row: own, inherited, or unset for each projected field. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeSources","in":"query","required":false,"description":"Include field_sources on each returned entity row, mapping fields to the canonical entity supplying their effective value or configuration unit. Independent of includeResolution; defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeOwnValues","in":"query","required":false,"description":"Include own_values on each returned entity row, retaining null local values. Requires editor authorization; otherwise returns 403. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"session":{"$ref":"#/components/schemas/CanonicalProvenanceRow"}},"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Canonical session bookkeeping and authorized history"},"403":{"description":"Provenance is unavailable in preview"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Read session bookkeeping and provenance","tags":["Organization catalog"],"x-auth":"Account cookie or header, or permitted machine key, from Starter up. Bookkeeping follows the existing field masks. Field provenance and raw histories require an ADMIN user-backed viewer, including an ADMIN-owned machine key; staff, bare service credentials, and membership upgrades do not grant this authority. All previews are denied.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/sessions/$SESSION_ID/provenance\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Read permitted session bookkeeping and provenance"}]}},"/api/sessions/{id}/score":{"get":{"description":"Returns one canonical session with its School’s Out session score as pure JSON: the assembled SessionScoreInput (dates, availability, sources, and its activity's and camp's scores), the full score whose bookability criterion carries no weight once the session has ended, and the scoring version. Registration evidence reads the session-owned registration dates; existing status precedence is unchanged. This endpoint calculates the live score and returns its input/evidence separately from normal catalog reads. The embedded canonical record follows the sparse public projection.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"additionalProperties":true,"type":"object"},"success":{"const":true}},"type":"object"}}},"description":"Live score, scoring input, canonical record, scoringVersion, tier, and lockedColumns. Organization responses also include moderation and optional peers."},"400":{"description":"Invalid record UUID."},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}},"summary":"Read one live session score","tags":["Organization catalog"],"x-auth":"Existing Starter+ score access and field masks apply. Active organization membership retains its scoped detail access.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/sessions/$SESSION_ID/score\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\"","title":"Read session live score detail"}]}},"/api/overseer/messages":{"post":{"description":"Persists a staff-authored request without editing standing instructions. An active worker delivers it to the current coordinator through live input; an idle worker starts a new turn. Supply a canonical UUID requestId; retry identical requestId, expectedRevision, text, entityConfigurationId and field after an uncertain result to retrieve its durable receipt, even after the active revision changes. Reusing an ID for different input returns 409. New stale requests return 409 with the current snapshot. The first request may initialize an empty revision when expectedRevision is zero. The current configuration editor remains the execution authority; the submitting operator is retained as message attribution. A maximum of 100 requests may be pending. Requests are delivered only after a valid successful agent turn; failed attempts retain them for recovery using committed action history. Requests accepted during an active turn may be handled and delivered by that turn. Input rejected or not dispatched before the turn finishes remains queued for a later turn. Replacing the configuration or clearing memory retains unfinished requests and saved progress. The returned expectedRevision remains the immutable submitted revision; revision identifies the current execution owner and may change when settings are replaced. Events user_message, message_delivered and message_cancelled identify their requestId; user_message also contains text. Delivered is transport acknowledgment, not proof of requested work completion. The saved owner is reauthorized before inference and tools; a message never takes over execution ownership. Revoking a sender after acceptance does not retroactively cancel the request. Unfinished delivered work can continue through the same revision checkpoint and native thread. A new stale submission includes top-level outcome=rejected and messageRejection={requestId,expectedRevision}; because historical receipt lookup preceded the monotonic revision check under one lock, the exact request cannot later be accepted. An uncertain client may release its retry only for a matching marker or a durable receipt. A generic 409 does not prove delivery outcome. Requests may be queued while paused; enqueue preserves the pause state and they wait for an explicit resume. Optional entityConfigurationId binds the request to exactly one live organization, activity, or session owning that configuration. Historical exact retries are checked before current ownership validation. Responses resolve targetKind, targetId and targetName for canonical tools; the configuration is never an edit target. Missing or ambiguous current ownership leaves these fields null. Optional field names the catalog field the request is about and must belong to the resolved record kind; it is returned unchanged and never widens the edit target. The user_message event retains the supplied entityConfigurationId and field. workStatus and progress track explicit agent-reported handling independently of delivery status; completed is an agent report, while correction feedback completion is derived from an applied version receipt.","requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"expectedRevision":{"type":"integer","minimum":0},"requestId":{"type":"string","format":"uuid"},"text":{"type":"string","minLength":1,"description":"Nonblank request, at most 16384 UTF-8 bytes. Original text is retained in its durable receipt."},"entityConfigurationId":{"type":"string","format":"uuid","description":"Optional exact record configuration_id. Included in immutable request identity; resolved server-side to an organization, activity, or session."},"field":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$","description":"Optional catalog field key the request is about, such as refund_policy, or media for the gallery, cover and logo. Requires entityConfigurationId and must be a field of the resolved record kind. Included in immutable request identity."}},"required":["expectedRevision","requestId","text"]}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerMessage"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Durable request receipt, including an identical earlier submission."},"400":{"description":"Missing, invalid, or unsupported request input."},"401":{"description":"Authentication required."},"403":{"description":"Current identity is not a user-backed staff or ADMIN operator, or the browser mutation lacks a trusted origin and session-bound CSRF token."},"409":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerSnapshot"},"error":{"const":true},"message":{"type":"string"},"success":{"const":false},"outcome":{"type":"string","const":"rejected","description":"Present with messageRejection only when the exact stale submission was not accepted and can never be accepted later."},"messageRejection":{"type":"object","additionalProperties":false,"properties":{"requestId":{"type":"string","format":"uuid"},"expectedRevision":{"type":"integer","minimum":0}},"required":["requestId","expectedRevision"],"description":"Definitive rejection identity after historical receipt lookup and stale revision comparison under the singleton lock. Match both fields before releasing an uncertain send."}},"required":["error","message"],"type":"object"}}},"description":"The current revision changed, or requestId was reused for different input. Current snapshot is included for a revision conflict."},"404":{"description":"The supplied configuration has no unambiguous live canonical record."}},"security":[{"ApiKeyAuth":[]}],"summary":"Queue a one-off Overseer request","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN identity. Account credentials and machine keys belonging to eligible users are accepted; a bare service credential is not. Intelligence uses its signed session cookie; cookie POST requests require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"title":"Queue a one-off request without changing instructions","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/messages\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"expectedRevision\":12,\"requestId\":\"eb4b5d5a-87b0-4cb4-a4f8-0e874584396a\",\"text\":\"Summarize the outstanding Washington reviews.\"}'"}],"x-internal":true}},"/api/overseer/pause":{"post":{"description":"Sets the global desired pause state using expectedRevision and expectedPauseVersion. Both must match the current snapshot, otherwise 409 returns the current snapshot. An unchanged desired state with matching versions is a no-op; an actual change increments only pauseVersion. Pause immediately fences the active turn and asks the supervisor to stop and join its native process and tools; existing execution occupancy remains until cleanup releases it or its lease expires. Instructions, tool controls, native thread, checkpoint, queued messages, editor authority, underlying work state and wake/backoff schedule are preserved. Resume restores eligibility under that schedule; it does not start an API-owned worker or bypass backoff. Save, message enqueue, review feedback and clear-memory never implicitly resume. Pause is supported before any saved instruction and does not create a revision. Existing human review approvals and already-dispatched child jobs remain independent. Paused execution creates no new scheduled progress reports or notification delivery claims, but preserves due timestamps and pending notices; already-claimed delivery may finish under its existing fence. pause_changed events contain paused and pauseVersion when a saved revision exists. After an uncertain response, read the current snapshot before a new explicit control request; do not automatically retry against refreshed versions.","requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"expectedRevision":{"type":"integer","minimum":0},"expectedPauseVersion":{"type":"integer","minimum":0},"paused":{"type":"boolean"}},"required":["expectedRevision","expectedPauseVersion","paused"]}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerSnapshot"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Current snapshot after the requested pause state was applied or was already current."},"400":{"description":"Missing, invalid, or unsupported request input."},"401":{"description":"Authentication required."},"403":{"description":"Current identity is not a user-backed staff or ADMIN operator, or the browser mutation lacks a trusted origin and session-bound CSRF token."},"409":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerSnapshot"},"error":{"const":true},"message":{"type":"string"},"success":{"const":false}},"required":["data","success","message"],"type":"object"}}},"description":"Instruction revision or pause version changed. Review the current snapshot before a new explicit request."}},"security":[{"ApiKeyAuth":[]}],"summary":"Pause or resume Overseer execution","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN identity. Account credentials and machine keys belonging to eligible users are accepted; a bare service credential is not. Intelligence uses its signed session cookie; cookie POST requests require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"title":"Pause the configuration reviewed at revision 12 and pause version 3","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/pause\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"expectedRevision\":12,\"expectedPauseVersion\":3,\"paused\":true}'"}],"x-internal":true}},"/api/users/viewer":{"get":{"description":"Returns the retained account payload with a camelCase profile beside the snake_case user.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyAccountViewer"}}},"description":"Success. Values are top-level (retained account response)."},"401":{"description":"Current account required"}},"security":[{"AccountCredential":[]}],"summary":"Read the signed-in account and its profile","tags":["Internal family account"],"x-auth":"Any current account credential (cookie or account key). Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read the signed-in account and its profile"}],"x-internal":true}},"/api/users/update-viewer":{"post":{"description":"Changes the username and/or the named profile fields; null clears a profile field. Unknown keys are refused. Email changes go through the verification flow instead. Editing the phone does not change the SMS second factor, so a new number is unverified. Answers with the GET /api/users/viewer payload.","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"username":{"pattern":"^[a-z0-9_-]{3,32}$","type":"string"},"profile":{"additionalProperties":false,"properties":{"fullName":{"maxLength":120,"minLength":1,"type":["string","null"]},"preferredName":{"maxLength":60,"minLength":1,"type":["string","null"]},"phone":{"description":"Any common format; stored as E.164. null clears it.","type":["string","null"]}},"type":"object"}},"required":[],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyAccountViewer"}}},"description":"Success. Values are top-level (retained account response)."},"400":{"description":"Invalid field, unknown key, or nothing to change"},"401":{"description":"Current account required"},"409":{"description":"Username taken"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"Edit the username or profile","tags":["Internal family account"],"x-auth":"Any current account credential (cookie or account key). Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/update-viewer\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"profile\":{\"fullName\":\"Jordan Okafor\",\"preferredName\":\"Jordan\"}}'","title":"Edit the username or profile"}],"x-internal":true}},"/api/users/viewer/sessions":{"get":{"description":"Live sessions, most recently active first (at most 100). Sessions are created by each sign-in and named by the session cookie's sid claim; cookies without a sid are not listed.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"sessions":{"items":{"$ref":"#/components/schemas/FamilyAccountDeviceSession"},"type":"array"}},"required":["sessions"],"type":"object","additionalProperties":false},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"List signed-in devices","tags":["Internal family account"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/sessions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"List signed-in devices"}],"x-internal":true}},"/api/users/viewer/sessions/{sessionId}":{"delete":{"description":"Revokes the session; the next request carrying its sid answers 401. Revoking the current session signs this device out.","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"revoked":{"const":true}},"required":["revoked"],"type":"object","additionalProperties":false},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"},"404":{"description":"Not the viewer's live session"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"Sign out one device","tags":["Internal family account"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/sessions/$SESSION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Sign out one device"}],"x-internal":true}},"/api/users/viewer/sessions/revoke-others":{"post":{"description":"Revokes every live session except the one this request's cookie names (all of them for a header credential). A password reset and a deletion request do the same.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"revoked":{"minimum":0,"type":"integer"}},"required":["revoked"],"type":"object","additionalProperties":false},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"Sign out every other device","tags":["Internal family account"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/sessions/revoke-others\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Sign out every other device"}],"x-internal":true}},"/api/users/sign-out":{"post":{"description":"Revokes the session the cookie's sid names. Cookies without a sid and header credentials name no session and answer revoked:false. The caller still clears its own cookie.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"revoked":{"type":"boolean"}},"required":["revoked"],"type":"object","additionalProperties":false},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"Sign out this device","tags":["Internal family account"],"x-auth":"Any current account credential (cookie or account key). Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/sign-out\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Sign out this device"}],"x-internal":true}},"/api/users/viewer/exports":{"post":{"description":"Queues an export. The worker builds one JSON archive of the account profile and every family record the viewer owns, then emails a download link that works until it expires after a few days. Answers 202.","responses":{"202":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"export":{"$ref":"#/components/schemas/FamilyAccountExport"}},"required":["export"],"type":"object","additionalProperties":false},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"},"429":{"description":"An export is unfinished or one was requested recently. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"security":[{"AccountCredential":[]}],"summary":"Request a copy of the family's data","tags":["Internal family account"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/exports\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Request a copy of the family's data"}],"x-internal":true}},"/api/users/viewer/exports/{exportId}":{"get":{"description":"The viewer's own export. The archive itself is only available through the emailed link.","parameters":[{"in":"path","name":"exportId","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"export":{"$ref":"#/components/schemas/FamilyAccountExport"}},"required":["export"],"type":"object","additionalProperties":false},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Current account required"},"404":{"description":"Not the viewer's export"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"summary":"Read a data export's status","tags":["Internal family account"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/exports/$EXPORT_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read a data export's status"}],"x-internal":true}},"/api/users/viewer/places":{"get":{"description":"Send exactly one of q (a US city, ZIP code or street address, 2 to 120 characters) or latitude and longitude (Use my location). Returns at most 5 places with whether School's Out serves each. A street address keeps its address and the geocoder's precision beside an area label; a reverse lookup's precision is device. A placeId is valid for one hour and only for this viewer, for the household's search area, home and places. Limited to 30 lookups a minute per account.","parameters":[{"in":"query","name":"q","schema":{"maxLength":120,"minLength":2,"type":"string"}},{"in":"query","name":"latitude","schema":{"maximum":90,"minimum":-90,"type":"number"}},{"in":"query","name":"longitude","schema":{"maximum":180,"minimum":-180,"type":"number"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"places":{"items":{"$ref":"#/components/schemas/FamilyAccountPlace"},"maxItems":5,"type":"array"}},"required":["places"],"type":"object","additionalProperties":false},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Neither or both input forms, or an invalid value"},"401":{"description":"Current account required"},"429":{"description":"Lookup rate limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."},"503":{"description":"Place search is unavailable"}},"security":[{"AccountCredential":[]}],"summary":"Find a place","tags":["Internal family account"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/places?q=97202\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Find a home area"}],"x-internal":true}},"/api/global-settings":{"get":{"description":"Every global on/off setting by name, each its stored value or else its default. signups_enabled (default true): false refuses every route that creates an account with code signups_disabled, while existing accounts still sign in. referral_only (default false): true makes a new account need a referralCode (referral_code on the legacy app's routes), else the route refuses with code referral_required. Settings are stored by snake_case name in global_settings and answered as camelCase fields; new settings add fields, which clients ignore until they know them. The sign-up routes enforce the settings themselves; this read is for clients deciding what to show.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"settings":{"properties":{"referralOnly":{"description":"Setting referral_only (default false): a new account needs a referral code.","type":"boolean"},"signupsEnabled":{"description":"Setting signups_enabled (default true): new accounts may be created.","type":"boolean"}},"required":["signupsEnabled","referralOnly"],"type":"object","additionalProperties":false}},"required":["settings"],"type":"object","additionalProperties":false},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"summary":"Read the global settings","tags":["Internal family account"],"x-auth":"Public.","x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/global-settings\"","title":"Read the global settings"}],"x-internal":true}},"/api/users/viewer/feature-flags":{"get":{"description":"The signed-in account's own feature flags, each its override or else its default. agentic_booking_preview (default false): the site runs agentic booking on mock data for this account. It grants no booking access.","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"flags":{"properties":{"agenticBookingPreview":{"description":"Flag agentic_booking_preview (default false): agentic booking runs on mock data.","type":"boolean"}},"required":["agenticBookingPreview"],"type":"object","additionalProperties":false}},"required":["flags"],"type":"object","additionalProperties":false},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"401":{"description":"Not signed in"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"summary":"Read the viewer's feature flags","tags":["Internal family account"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level.","x-curl-examples":[{"code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/feature-flags\" -H \"x-api-key: $API_KEY\"","title":"Read the viewer's feature flags"}],"x-internal":true}},"/api/users/viewer/referral-redemptions":{"post":{"description":"For new accounts (created within the last 30 days) at sign-up or onboarding. Each account redeems one code, never its own household's. The referring household's credit is granted through the wallet, possibly later. Limited to 10 attempts an hour.","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"code":{"type":"string"}},"required":["code"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"redemption":{"$ref":"#/components/schemas/FamilyAccountReferralRedemption"}},"required":["redemption"],"type":"object","additionalProperties":false},"success":{"const":true,"type":"boolean"}},"required":["success","data"],"type":"object"}}},"description":"Success"},"400":{"description":"Malformed code or the household's own code"},"401":{"description":"Current account required"},"404":{"description":"Unknown code"},"409":{"description":"Already redeemed, the account is not new, or the code has admitted as many families as it can (code referral_limit_reached)"},"429":{"description":"Attempt limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"security":[{"AccountCredential":[]}],"summary":"Redeem another household's referral code","tags":["Internal family account"],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/referral-redemptions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"OKAFOR-24\"}'","title":"Redeem another household's referral code"}],"x-internal":true}},"/api/users/viewer/google-calendar":{"get":{"summary":"Read the first Google Calendar connection","description":"Returns the viewer's first Google connection (in connection order) in the single-connection shape, without its token; without one, connected: false, nulls and the default sync choices. For app releases before multiple accounts; GET /api/users/viewer/calendars lists every connection. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountGoogleCalendarConnection"}}}}}},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-curl-examples":[{"title":"Read the connection","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/google-calendar\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"patch":{"summary":"Change the first Google Calendar connection's choices","description":"As PATCH /api/users/viewer/calendars/{connectionId}, for the viewer's first Google connection, answering in the single-connection shape; 404 when the viewer has none. For app releases before multiple accounts. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountGoogleCalendarConnection"}}}}}},"400":{"description":"Invalid input."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"404":{"description":"The viewer has no Google connection."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-curl-examples":[{"title":"Also sync waitlisted sessions","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/google-calendar\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sync\":{\"waitlisted\":true}}'"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"sync":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"confirmed":{"type":"boolean"},"waitlisted":{"type":"boolean"}}},"calendars":{"type":"array","maxItems":50,"items":{"type":"string"},"description":"Replaces the reading selection with these calendar IDs, all from calendars. The cache refreshes on the next read."}},"minProperties":1}}}}},"delete":{"summary":"Disconnect the first Google Calendar connection","description":"As DELETE /api/users/viewer/calendars/{connectionId}, for the viewer's first Google connection; without one it answers {connected: false, revoked: false}. For app releases before multiple accounts. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountCalendarDisconnection"}}}}}},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"409":{"description":"A grant was reconnected during revocation; retry against current state."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"501":{"description":"Google Calendar OAuth and token-sealing settings are not configured."},"502":{"description":"Google revocation failed; the stored connection is retained."}},"x-curl-examples":[{"title":"Disconnect Google Calendar","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/google-calendar\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/google-calendar/authorization":{"post":{"summary":"Start a browser or app Google Calendar connection","description":"Starts Google Calendar access (calendar.events.readonly and calendar.app.created, required, plus calendar.calendarlist.readonly, which the parent may decline; offline, with include_granted_scopes so an earlier grant is extended rather than replaced) and returns {url}. Web requests use an optional parent-site redirect and receive the browser nonce cookie. Proof requests send {redirect, challenge}: exactly schoolsout://google-calendar-callback (or exp://host:port/--/google-calendar-callback outside production), or a parent-site page when the site's server (a BFF) calls the API, and an unpadded base64url SHA-256 proof of a verifier the client keeps; their URL is a one-use 10-minute API browser bootstrap that sets the nonce in the browser, and the client completes with the verifier (POST .../google-calendar/complete). As for Gmail. Each Google account the parent grants becomes a connection of its own; granting an account already connected replaces that connection's grant. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountGoogleCalendarAuthorization"}}}}}},"400":{"description":"Redirect not allowed, malformed proof, or unknown property."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"501":{"description":"Google Calendar OAuth and token-sealing settings are not configured."}},"x-curl-examples":[{"title":"Start a Google Calendar connection from the installed app","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/google-calendar/authorization\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"redirect\":\"schoolsout://google-calendar-callback\",\"challenge\":\"$CHALLENGE\"}'"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":[],"properties":{"redirect":{"type":"string","maxLength":2048,"description":"Allowed parent page for web, schoolsout://google-calendar-callback for installed apps, or the narrow Expo Go callback in development."},"challenge":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$","description":"Unpadded base64url SHA-256 of the app-held verifier. Required for native callbacks and forbidden for web callbacks."}}}}}}}},"/api/users/viewer/google-calendar/complete":{"post":{"summary":"Complete a native Google Calendar connection","description":"Native completion accepts {code, verifier}. Consumes the five-minute ticket only for its starting adult; the original S256 proof must match a 43–128 character verifier. Exchanges the sealed Google authorization code, requires both scopes, and saves the grant as the viewer's connection to that Google account (a new connection, or the existing one whose grant it replaces; 409 when a new account would pass the 10-connection limit). It makes School's Out's own calendar in that account (bookings or not; a reconnection keeps it), refreshes its event cache at once and returns that connection in the single-connection shape (lastSyncedAt set; a failed first refresh, or a calendar Google will not make (push_failed), is recorded as failure, the grant is kept and the worker retries it). The family's bookings are pushed by the worker's next pass. Missing or wrong proof, expiry, replay and a partial grant return 400. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountGoogleCalendarConnection"}}}}}},"400":{"description":"Invalid, expired or reused flow, wrong proof, or a rejected Google grant."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"409":{"description":"Connecting a new account would pass the 10-connection limit."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"501":{"description":"Google Calendar OAuth and token-sealing settings are not configured."}},"x-curl-examples":[{"title":"Complete the app connection","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/google-calendar/complete\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"$COMPLETION\",\"verifier\":\"$VERIFIER\"}'"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["code","verifier"],"properties":{"code":{"type":"string","format":"uuid"},"verifier":{"type":"string","minLength":43,"maxLength":128}}}}}}}},"/api/users/viewer/google-calendar/events":{"get":{"summary":"Read the family's cached calendar events (earlier path)","description":"The same read as GET /api/users/viewer/calendars/events, at the path app releases before multiple accounts use. Every role may read it.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountCalendarEvents"}}}}}},"400":{"description":"Missing or invalid dates, to before from, or a range over 400 days."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-curl-examples":[{"title":"Read October's events","code":"curl --get \"https://apis.schoolsoutapp.com/api/users/viewer/google-calendar/events\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode from=2026-10-01 --data-urlencode to=2026-10-31"}],"parameters":[{"in":"query","name":"from","required":true,"schema":{"type":"string","format":"date"},"description":"Inclusive local date (YYYY-MM-DD)."},{"in":"query","name":"to","required":true,"schema":{"type":"string","format":"date"},"description":"Inclusive local date (YYYY-MM-DD)."}]}},"/api/users/viewer/calendars":{"get":{"summary":"List the calendar connections","description":"Returns the viewer's own calendar connections, without their secrets, in the order they were first connected: every Google account (connect each through POST .../google-calendar/authorization; connecting an account already connected replaces its grant) and Apple iCloud account. Each connection has its own sync choices, calendars and failure. An adult has at most 10 connections. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connections; nobody manages another adult's.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountCalendarConnections"}}}}}},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-curl-examples":[{"title":"List the connections","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/calendars\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/calendars/apple":{"post":{"summary":"Connect an Apple iCloud calendar","description":"Connects an Apple iCloud account with POST {appleId, password}: the Apple ID and an app-specific password the parent makes at account.apple.com (xxxx-xxxx-xxxx-xxxx; an Apple ID's own password never works and is refused before asking Apple). Apple checks them first over CalDAV (discovering the account's calendar home); a refused Apple ID or password returns 400 and stores nothing, and iCloud being unreachable returns 502. The password is sealed for the viewer (APPLE_CALENDAR_TOKEN_KEY) and saved as the viewer's connection to that Apple ID: a new one, or the existing one whose password it replaces; 409 when a new account would pass the 10-connection limit. As for a Google account, School's Out makes its own calendar in the account (the only one it writes; bookings are pushed by the worker's next pass), every other calendar is listed and read, the cache is refreshed at once, and the connection is returned (a failed first refresh is recorded as failure and retried by the worker). A password the parent later revokes reads as failure authorization_revoked until they connect again. 501 without APPLE_CALENDAR_TOKEN_KEY. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connections; nobody manages another adult's.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountCalendarConnection"}}}}}},"400":{"description":"Invalid input, or Apple refused the Apple ID or app-specific password."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"409":{"description":"Connecting a new account would pass the 10-connection limit."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"501":{"description":"Apple iCloud calendars are not configured."},"502":{"description":"iCloud could not be reached."}},"x-curl-examples":[{"title":"Connect an Apple iCloud account","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/calendars/apple\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"appleId\":\"parent@icloud.com\",\"password\":\"abcd-efgh-ijkl-mnop\"}'"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["appleId","password"],"properties":{"appleId":{"type":"string","format":"email","maxLength":320,"description":"The Apple ID's email address."},"password":{"type":"string","pattern":"^[A-Za-z]{4}-?[A-Za-z]{4}-?[A-Za-z]{4}-?[A-Za-z]{4}$","description":"An app-specific password from account.apple.com."}}}}}}}},"/api/users/viewer/calendars/{connectionId}":{"patch":{"summary":"Change a calendar connection's choices","description":"PATCH {sync?: {confirmed?, waitlisted?}, calendars?: string[]} changes which of the family's bookings the worker writes to School's Out's own calendar in this account, and which of the account's calendars are read. Unknown properties, non-boolean sync values and calendar IDs that are not in calendars return 400; 404 when the viewer has no such connection. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connections; nobody manages another adult's.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountCalendarConnection"}}}}}},"400":{"description":"Invalid input."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"404":{"description":"The viewer has no such connection."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-curl-examples":[{"title":"Also sync waitlisted sessions","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/calendars/$CONNECTION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sync\":{\"waitlisted\":true}}'"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"sync":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"confirmed":{"type":"boolean"},"waitlisted":{"type":"boolean"}}},"calendars":{"type":"array","maxItems":50,"items":{"type":"string"},"description":"Replaces the reading selection with these calendar IDs, all from calendars. The cache refreshes on the next read."}},"minProperties":1}}}},"parameters":[{"in":"path","name":"connectionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"A connection ID from GET /api/users/viewer/calendars."}]},"delete":{"summary":"Disconnect a calendar connection","description":"Disconnects one of the viewer's connections: deletes School's Out's own calendar from the account (and so every booking event School's Out wrote there; best effort, it never blocks the disconnect), revokes a Google grant at Google, then deletes the connection and its cached events and returns {connected: false, revoked}. A token Google already considers invalid counts as revoked. A Google revocation failure returns 502 and keeps the connection; a connection reconnected during the request returns 409. Apple cannot revoke an app-specific password for the parent, so an Apple disconnect answers revoked: false; the parent revokes the password at account.apple.com. 404 when the viewer has no such connection. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connections; nobody manages another adult's.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountCalendarDisconnection"}}}}}},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"404":{"description":"The viewer has no such connection."},"409":{"description":"The account was reconnected while disconnecting; try again."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"502":{"description":"The provider did not confirm the revocation; the connection is kept."}},"x-curl-examples":[{"title":"Disconnect an account","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/calendars/$CONNECTION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"in":"path","name":"connectionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"A connection ID from GET /api/users/viewer/calendars."}]}},"/api/users/viewer/calendars/events":{"get":{"summary":"Read the family's cached calendar events","description":"Returns the family's merged calendar: the cached events, overlapping the inclusive local date range in the household time zone, of every connection of every adult of the family the viewer acts in (its owner, co-parents and caregivers). The viewer's own events are whole; each other adult's event is a busy block with only its time span and the adult's name (busy: true; no title, calendar, link or children), enforced in this response. An event an adult has in several calendars or accounts (one event ID) appears once, and another adult's copy of an event the viewer has is left out. When one of the viewer's own caches is older than the refresh interval, the request refreshes it from its provider first; other adults' caches are refreshed by the worker. A failed refresh serves cached events with stale: true and is recorded on its connection. A family with no connected adult answers events: []. from and to are YYYY-MM-DD, to on or after from, covering at most 400 days. Each account's events come from the calendars chosen on its connection; a Google grant without the calendar-list scope reads only the primary calendar. Every role may read it.","tags":["Internal family account"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Cookie requests require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyAccountCalendarEvents"}}}}}},"400":{"description":"Missing or invalid dates, to before from, or a range over 400 days."},"401":{"description":"Current adult account required; service delegation and machine keys are refused."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-curl-examples":[{"title":"Read October's events","code":"curl --get \"https://apis.schoolsoutapp.com/api/users/viewer/calendars/events\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode from=2026-10-01 --data-urlencode to=2026-10-31"}],"parameters":[{"in":"query","name":"from","required":true,"schema":{"type":"string","format":"date"},"description":"Inclusive local date (YYYY-MM-DD)."},{"in":"query","name":"to","required":true,"schema":{"type":"string","format":"date"},"description":"Inclusive local date (YYYY-MM-DD)."}]}},"/api/auth/google-calendar-start":{"get":{"summary":"Open native Google Calendar consent in its authentication browser","description":"Consumes ticket, sets the HttpOnly callback nonce in the system browser, then redirects to Google Calendar consent. Expired or replayed tickets return 400. No account credential is accepted in a URL; the browser step alone cannot store a connection.","tags":["Internal Google Calendar"],"x-internal":true,"security":[],"x-auth":"Public, independent-auth browser navigation. A one-use 10-minute native bootstrap ticket identifies the starting account and app proof.","responses":{"302":{"description":"Google consent redirect with the browser nonce cookie; no-store and no-referrer."},"400":{"description":"Invalid, expired or reused ticket."}},"x-curl-examples":[{"title":"Open the native bootstrap redirect","code":"curl -i -X GET \"https://apis.schoolsoutapp.com/api/auth/google-calendar-start?ticket=$TICKET\""}],"parameters":[{"in":"query","name":"ticket","required":true,"schema":{"type":"string","format":"uuid"},"description":"One-time native bootstrap ticket."}]}},"/api/auth/google-calendar-callback":{"get":{"summary":"Return from Google Calendar consent","description":"Consumes the Google state and requires the starting browser nonce. Web flow exchanges the code, requires both Calendar scopes, stores the sealed grant as the viewer's connection to that Google account, makes School's Out's own calendar and refreshes its event cache at once (as the native completion does), and returns calendar=connected, denied or failed. A proof flow (app or a site's server) only seals a short-lived authorization code and returns calendar=authorized&code=<one-use completion> to its redirect; denial or failure includes no code. Every valid-state proof outcome echoes challenge=<original public S256>. A refusal names no account, so it changes no existing connection.","tags":["Internal Google Calendar"],"x-internal":true,"security":[],"x-auth":"This endpoint is open to the public (Google redirect target); the one-time state and the starting browser's nonce cookie identify the parent","responses":{"302":{"description":"Allowed parent page or app callback with calendar=connected|authorized|denied|failed and, for native, a one-use completion code."}},"x-curl-examples":[{"title":"Return from Google consent","code":"curl -i -X GET \"https://apis.schoolsoutapp.com/api/auth/google-calendar-callback?state=$STATE&code=$GOOGLE_CODE\" \\\n  -H \"Cookie: $CALENDAR_NONCE_COOKIE\""}],"parameters":[{"in":"query","name":"state","required":true,"schema":{"type":"string"},"description":"One-time browser state, checked against its nonce cookie."},{"in":"query","name":"code","required":false,"schema":{"type":"string"},"description":"Google authorization code; never forwarded to the app in plaintext."},{"in":"query","name":"error","required":false,"schema":{"type":"string"},"description":"Google denial indicator; raw provider error text is not forwarded."}]}},"/api/schools":{"get":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Search schools","description":"Finds schools by name (q, trigram word similarity), by nearness (near), or both; at least one is required. Name matches come first by similarity, then nearest; near alone orders nearest first. near=home is the family's household's saved search-area point (a co-parent's or caregiver's home is the family owner's), else the centre of its search-area ZIP code; with no home known and no q it finds nothing. distanceMiles is always from home. limit is 1 to 20 (default 10).","responses":{"200":{"description":"Matching schools.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["schools"],"properties":{"schools":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlanSchool"}}}}}},"example":{"success":true,"data":{"schools":[{"id":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Lincoln Elementary School","kind":"public","city":"Portland","region":"OR","postalCode":"97202","distanceMiles":0.7,"calendarStatus":"published"}]}}}}},"400":{"description":"Neither q nor near, q outside 2 to 120 characters, near not home or latitude,longitude, or limit outside 1 to 20."},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted origin.","x-curl-examples":[{"title":"Search schools","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/schools?q=lincoln&near=home\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"in":"query","name":"q","schema":{"type":"string","minLength":2,"maxLength":120}},{"in":"query","name":"near","schema":{"type":"string"},"description":"home, or latitude,longitude."},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":20}}]}},"/api/users/viewer/children/{childId}/school":{"get":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Read a child's school","description":"The school the child attends, or the parent's other answer; null when the parent has not answered. The child's owner and guardians may read it.","responses":{"200":{"description":"The child's school.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["school"],"properties":{"school":{"oneOf":[{"$ref":"#/components/schemas/FamilyPlanChildSchool"},{"type":"null"}]}}}}},"example":{"success":true,"data":{"school":{"schoolId":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Lincoln Elementary School","kind":"public"}}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"description":"Child not available to this account."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted origin.","x-curl-examples":[{"title":"Read a child's school","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/school\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"in":"path","name":"childId","required":true,"schema":{"type":"string","format":"uuid"}}]},"put":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Set a child's school","description":"Records the school a child attends ({schoolId}) or another answer ({other: private|homeschool|not_found, name?}). Only the child's owner may change it; a guardian gets 403. Unknown properties are rejected.","responses":{"200":{"description":"The child's school.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["school"],"properties":{"school":{"$ref":"#/components/schemas/FamilyPlanChildSchool"}}}}},"example":{"success":true,"data":{"school":{"schoolId":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Lincoln Elementary School","kind":"public"}}}}}},"400":{"description":"Neither or both of schoolId and other, an unknown property, a malformed schoolId, an unknown other answer, or a name outside 1 to 120 characters."},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"403":{"description":"The viewer is the child's guardian, not its owner; The role may not add or edit children (edit_children; a caregiver)"},"404":{"description":"Child or school not found."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted origin.","x-curl-examples":[{"title":"Set a child's school","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/school\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\"schoolId\": \"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34\"}'"}],"parameters":[{"in":"path","name":"childId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"required":["schoolId"],"properties":{"schoolId":{"type":"string","format":"uuid"}}},{"type":"object","additionalProperties":false,"required":["other"],"properties":{"other":{"enum":["private","homeschool","not_found"]},"name":{"type":"string","minLength":1,"maxLength":120}}}]},"example":{"schoolId":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34"}}}}},"delete":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Forget a child's school","description":"Removes the child's school answer. Only the child's owner may.","responses":{"200":{"description":"The answer was removed.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["school"],"properties":{"school":{"type":"null"}}}}},"example":{"success":true,"data":{"school":null}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"403":{"description":"The viewer is the child's guardian, not its owner; The role may not add or edit children (edit_children; a caregiver)"},"404":{"description":"Child not available to this account."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted origin.","x-curl-examples":[{"title":"Forget a child's school","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/school\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"in":"path","name":"childId","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/users/viewer/school-calendar":{"get":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Read the school calendar","description":"Each child's school with its days out and early releases between from and to (inclusive household dates; default today to 120 days ahead; at most 400 days), after the family's overrides. Children at one school share an entry unless their own child-specific overrides in the range differ. A school-wide override applies to every child at the school; a child's own override beats it; in_session removes a published day. A child whose answer names no school is an entry of its own with schoolId null and status none; children without an answer are left out.","responses":{"200":{"description":"The school calendar.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyPlanSchoolCalendar"}}},"example":{"success":true,"data":{"schools":[{"schoolId":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Lincoln Elementary School","childIds":["1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93"],"status":"published","term":{"start":"2026-09-01","end":"2027-06-15"},"weekdays":[0,1,2,3,4],"hours":{"start":"08:00","end":"14:40"},"days":[{"date":"2026-10-21","kind":"early_release","label":"Early Release Day","breakId":null,"breakName":null,"endsAt":"12:30","source":"school","updateId":null},{"date":"2026-11-25","kind":"break","label":"Fall break","breakId":"2026-11-25","breakName":"Fall break","endsAt":null,"source":"school","updateId":null},{"date":"2026-12-02","kind":"no_school","label":"Snow day","breakId":null,"breakName":null,"endsAt":null,"source":"email","updateId":"6f1d2c3b-4a5e-4f60-8b7a-1c2d3e4f5a6b"}]}]}}}}},"400":{"description":"from or to not a date, to before from, or a range over 400 days."},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted origin.","x-curl-examples":[{"title":"Read the school calendar","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/school-calendar?from=2026-10-16&to=2027-02-13\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"in":"query","name":"from","schema":{"type":"string","format":"date"}},{"in":"query","name":"to","schema":{"type":"string","format":"date"}}]}},"/api/users/viewer/plans":{"get":{"tags":["Internal family calendar"],"x-internal":true,"summary":"List plans","description":"Merges the viewer's Hallpass bookings (read through the booking service, never copied) with plans made here, including plans another adult made for a child the viewer shares (childIds then lists only the children the viewer can see), ordered by start date and then id, a page at a time. A group filter may return a short page with a cursor; keep reading until nextCursor is null. The source, activityId and sessionId filters combine with the others.","parameters":[{"name":"childId","in":"query","required":false,"description":"Only this child's plans.","schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","required":false,"description":"Plans that end on or after this date; also bounds dates.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Plans that start on or before this date; also bounds dates.","schema":{"type":"string","format":"date"}},{"name":"group","in":"query","required":false,"description":"Only plans in this group.","schema":{"enum":["active","pending","waitlist","closed","past"]}},{"name":"source","in":"query","required":false,"description":"Only plans from this source. booking reads only Hallpass bookings; external and covered read no bookings.","schema":{"enum":["booking","external","covered"]}},{"name":"activityId","in":"query","required":false,"description":"Only plans of this catalog activity.","schema":{"type":"string","format":"uuid"}},{"name":"sessionId","in":"query","required":false,"description":"Only plans of this catalog session. With source=external and a childId, this is how a client checks whether a child already has a plan of the session before adding one.","schema":{"type":"string","format":"uuid"}},{"name":"cursor","in":"query","required":false,"description":"nextCursor of the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"A page of plans.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["plans","nextCursor"],"properties":{"plans":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlan"}},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}]}}}}},"example":{"success":true,"data":{"plans":[{"id":"plan:3b1e6f0a-2c4d-4e8f-9a1b-5c6d7e8f9a0b","source":"external","childIds":["1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93"],"title":"Saturday swim","organizationId":null,"activityId":null,"sessionId":null,"dates":["2026-10-17","2026-10-24"],"startTime":"09:00","endTime":"10:00","recurrence":{"weekdays":[5],"from":"2026-10-17","until":"2026-10-24"},"status":"registered","askAgainAfter":null,"group":"active","coveredBy":null,"note":null,"bookingId":null,"editable":true}],"nextCursor":null}}}}},"400":{"description":"A parameter is malformed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"group must be active, pending, waitlist, closed or past.","data":null}}}},"404":{"description":"childId is not one of the viewer's children.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Child not found.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"List active plans","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/plans?group=active&from=2026-10-01&to=2026-12-31\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Add a plan","description":"Records a registration made on a provider's own site (external) or days the family covers itself (covered). A sessionId or activityId stores the catalog's organization and activity and, without a title, its name. An external plan of a session may leave out dates and recurrence: the plan then takes the session's days (the attendance dates its schedule lists, weekly rules on their weekdays without excluded dates, and every day of a date range from its start to its end, or only its start when it states no end; at most 400, earliest first) and, when startTime and endTime are left out too, the session's one daily clock if it has one. A session that states no dates answers 400. Explicit dates or a recurrence always win. An external plan of a catalog session (or, without a sessionId, of an activity) is recorded at most once per child: a child that already has an open (not canceled) external plan of the same session (or of the same activity with no session), made by any adult of the family, is not planned again. That plan keeps its dates, times and children, and moves to the requested status only when the status is further along unconfirmed < needs_parent < applied < registered and the viewer may change the plan (editable); it never moves back. The other children get one new plan. The answer is 201 with created true and the new plan when a plan was made, else 200 with created false and plan the oldest of existingPlans. existingPlans lists the open plans that already covered some of the children, as they read after any status change. Title-only external plans and covered plans are never matched and always answer 201 with an empty existingPlans.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanInput"}}}},"responses":{"200":{"description":"Every child already had an open external plan of the session (or activity): nothing new was made. plan is the oldest of existingPlans.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["plan","created","existingPlans"],"properties":{"plan":{"$ref":"#/components/schemas/FamilyPlan"},"created":{"type":"boolean","description":"true when this request made a new plan; false when every child already had an open external plan of the target."},"existingPlans":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlan"},"description":"Open external plans of the same session (or activity) that already covered some of the requested children, after any status advance, oldest first."}}}}},"example":{"success":true,"data":{"plan":{"id":"plan:3b1e6f0a-2c4d-4e8f-9a1b-5c6d7e8f9a0b","source":"external","childIds":["1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93"],"title":"Robotics week","organizationId":"7c2d9e41-5a3b-4f6c-8d1e-2b4a6c8e0f13","activityId":"9e4f1a2b-3c5d-4e6f-8a7b-1c2d3e4f5a6b","sessionId":"5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d","dates":["2027-06-21","2027-06-22","2027-06-23","2027-06-24","2027-06-25"],"startTime":"09:00","endTime":"15:00","recurrence":null,"status":"registered","askAgainAfter":null,"group":"active","coveredBy":null,"note":null,"bookingId":null,"editable":true},"created":false,"existingPlans":[{"id":"plan:3b1e6f0a-2c4d-4e8f-9a1b-5c6d7e8f9a0b","source":"external","childIds":["1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93"],"title":"Robotics week","organizationId":"7c2d9e41-5a3b-4f6c-8d1e-2b4a6c8e0f13","activityId":"9e4f1a2b-3c5d-4e6f-8a7b-1c2d3e4f5a6b","sessionId":"5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d","dates":["2027-06-21","2027-06-22","2027-06-23","2027-06-24","2027-06-25"],"startTime":"09:00","endTime":"15:00","recurrence":null,"status":"registered","askAgainAfter":null,"group":"active","coveredBy":null,"note":null,"bookingId":null,"editable":true}]}}}}},"201":{"description":"A new plan was made (for the children that had no open external plan of the target).","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["plan","created","existingPlans"],"properties":{"plan":{"$ref":"#/components/schemas/FamilyPlan"},"created":{"type":"boolean","description":"true when this request made a new plan; false when every child already had an open external plan of the target."},"existingPlans":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlan"},"description":"Open external plans of the same session (or activity) that already covered some of the requested children, after any status advance, oldest first."}}}}},"example":{"success":true,"data":{"plan":{"id":"plan:3b1e6f0a-2c4d-4e8f-9a1b-5c6d7e8f9a0b","source":"external","childIds":["1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93"],"title":"Saturday swim","organizationId":null,"activityId":null,"sessionId":null,"dates":["2026-10-17","2026-10-24"],"startTime":"09:00","endTime":"10:00","recurrence":{"weekdays":[5],"from":"2026-10-17","until":"2026-10-24"},"status":"registered","askAgainAfter":null,"group":"active","coveredBy":null,"note":null,"bookingId":null,"editable":true},"created":true,"existingPlans":[]}}}}},"400":{"description":"The body is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Send either dates or recurrence.","data":null}}}},"404":{"description":"A child is not the viewer's, or the session or activity does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Session not found.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not add or change plans (write_plans; a caregiver), or the plan is another adult's and the viewer is not the owner"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Add a weekly class","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/plans\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"childIds\": [\"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93\"], \"source\": \"external\", \"title\": \"Saturday swim\", \"recurrence\": {\"weekdays\": [5], \"from\": \"2026-10-17\", \"until\": null}, \"startTime\": \"09:00\", \"endTime\": \"10:00\"}'"}]}},"/api/users/viewer/plans/email-check":{"post":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Check email for registrations","description":"Runs only when the parent asks. Reads the viewer's own connected Gmail (gmail.readonly) for the last 60 days of mail whose subject reads like a registration confirmation, at most 20 messages, headers and Gmail's snippet only: no body or attachment is read or stored. Promotions and subjects that announce or chase a registration (\"now open\", \"reminder\") are skipped. Each message is matched to one organization the viewer saved or whose camp, activity or session page they viewed in the last 90 days: by the sender's domain (the organization's website domain or a subdomain of it, or its contact address's domain, never a public mailbox provider), else by the organization's name in the subject or the sender's name. It is then tied to one upcoming session of that organization (a session or activity name in the subject or snippet, a session the viewer viewed, or the organization's only upcoming session) and to the children the subject or snippet names by first name (or the only child the viewer sees). Each fully matched message becomes, once per Gmail message, a plan update (kind question, source email: the message id, the matched ids and the subject, at most 200 characters) and an unconfirmed external plan of the session with the session's days and daily clock, for the children without an open external plan of it already. Home then asks \"From your email: did you register?\" and the parent answers through PATCH /api/users/viewer/plans/{planId} (registered or canceled); nothing is ever confirmed without that answer. The update is undoable (deleting the plan) when the viewer is the family's owner. Messages from an organization the check could not tie to one session or to the children are listed in unresolved and recorded nowhere; they are listed again on the next check.","responses":{"200":{"description":"What the check found.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["checked","added","unresolved"],"properties":{"checked":{"type":"integer","minimum":0,"description":"Messages read."},"added":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPlan"},"description":"The unconfirmed plans this check made."},"unresolved":{"type":"array","items":{"$ref":"#/components/schemas/FamilyEmailCheckUnresolved"}}}}}},"example":{"success":true,"data":{"checked":7,"added":[{"id":"plan:3b1e6f0a-2c4d-4e8f-9a1b-5c6d7e8f9a0b","source":"external","childIds":["1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93"],"title":"Robotics week","organizationId":"7c2d9e41-5a3b-4f6c-8d1e-2b4a6c8e0f13","activityId":"9e4f1a2b-3c5d-4e6f-8a7b-1c2d3e4f5a6b","sessionId":"5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d","dates":["2027-06-21","2027-06-22","2027-06-23","2027-06-24","2027-06-25"],"startTime":"09:00","endTime":"15:00","recurrence":null,"status":"unconfirmed","askAgainAfter":null,"group":"pending","coveredBy":null,"note":null,"bookingId":null,"editable":true}],"unresolved":[{"messageId":"18c2f0a9b7d3e4f1","organizationId":"7c2d9e41-5a3b-4f6c-8d1e-2b4a6c8e0f13","organizationName":"Portland Robotics","sessionId":null,"activityId":null,"reason":"session"}]}}}}},"403":{"description":"The role may not add plans (write_plans), or the request is not the viewer's own account."},"409":{"description":"Gmail is not connected, lacks the read permission, or its grant expired or was revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Connect Gmail in Connected accounts so Mellow can help with your email.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"501":{"description":"The API has no Gmail settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Email checks are not available.","data":null}}}},"502":{"description":"Gmail could not be reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Gmail could not be reached. Try again in a moment.","data":null}}}}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Check email for registrations","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/plans/email-check\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/plans/{planId}":{"patch":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Change a plan","description":"Marks an external plan registered, cancels a plan, replaces its dates, or sets its note.","parameters":[{"name":"planId","in":"path","required":true,"description":"\"plan:<uuid>\"; a \"booking:<uuid>\" answers 409.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanChangeInput"}}}},"responses":{"200":{"description":"The changed plan.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["plan"],"properties":{"plan":{"$ref":"#/components/schemas/FamilyPlan"}}}}}}}},"400":{"description":"The body is invalid, or registered was sent for a covered plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Only an external plan can be marked registered.","data":null}}}},"403":{"description":"The plan is for a shared child but another adult made it; The role may not add or change plans (write_plans; a caregiver), or the plan is another adult's and the viewer is not the owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Only the parent who made this plan can change it.","data":null}}}},"404":{"description":"No such plan of the viewer's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Plan not found.","data":null}}}},"409":{"description":"A booking plan changes through its booking.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"A booking changes through the booking, not as a plan.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Mark registered","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/plans/plan:$PLAN_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"status\": \"registered\"}'"}]},"delete":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Delete a plan","description":"Deletes a plan made here. Bookings are cancelled with the booking cancel route instead.","parameters":[{"name":"planId","in":"path","required":true,"description":"\"plan:<uuid>\"; a \"booking:<uuid>\" answers 409.","schema":{"type":"string"}}],"responses":{"200":{"description":"The plan is gone.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["deleted"],"properties":{"deleted":{"const":true}}}}},"example":{"success":true,"data":{"deleted":true}}}}},"403":{"description":"The plan is for a shared child but another adult made it; The role may not add or change plans (write_plans; a caregiver), or the plan is another adult's and the viewer is not the owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Only the parent who made this plan can change it.","data":null}}}},"404":{"description":"No such plan of the viewer's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Plan not found.","data":null}}}},"409":{"description":"A booking plan changes through its booking.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"A booking changes through the booking, not as a plan.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Delete a plan","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/plans/plan:$PLAN_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/day-marks/{date}":{"put":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Mark a day","description":"Merges the family's answers for one day: each present key replaces the stored one and null clears it. A day left with no marks is removed. The date must be from 30 days before to 400 days after today in the household's time zone.","parameters":[{"name":"date","in":"path","required":true,"description":"Household date.","schema":{"type":"string","format":"date"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanDayMarks"}}}},"responses":{"200":{"description":"The day's marks after the merge.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["date","marks"],"properties":{"date":{"type":"string","format":"date"},"marks":{"$ref":"#/components/schemas/FamilyPlanDayMarks"}}}}},"example":{"success":true,"data":{"date":"2026-11-25","marks":{"open":"open","children":{"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93":{"need":"not_needed"}}}}}}}},"400":{"description":"The date or a mark is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Unknown day mark: color.","data":null}}}},"404":{"description":"A child is not the viewer's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Child not found.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not add or change plans (write_plans; a caregiver), or the plan is another adult's and the viewer is not the owner"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Share an open day","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/day-marks/2026-11-25\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"open\": \"open\"}'"}]}},"/api/users/viewer/calendar":{"get":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Read the calendar","description":"Composes, per day and child: the school calendar, bookings and plans, the family's day marks, registration openings and friends' open days; with detail=day also the cached Google events and each child's entries. A gap is a no-school or break day on a usual school day, with no planned cover, during which the household's caregiver (when set) is not available for the school's hours.","parameters":[{"name":"from","in":"query","required":false,"description":"First day; default today.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Last day; at most 400 days from from. Default about five weeks from from.","schema":{"type":"string","format":"date"}},{"name":"childId","in":"query","required":false,"description":"Only this child.","schema":{"type":"string","format":"uuid"}},{"name":"detail","in":"query","required":false,"description":"day adds entries and events and covers at most 45 days.","schema":{"enum":["day","summary"],"default":"summary"}}],"responses":{"200":{"description":"The days of the range and per-month counts.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyPlanCalendar"}}}}}},"400":{"description":"The range or detail is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"detail=day covers at most 45 days.","data":null}}}},"404":{"description":"childId is not one of the viewer's children.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Child not found.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Read a month","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/calendar?from=2026-11-01&to=2026-11-30&detail=day\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/home":{"get":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Read Home","description":"Home's cards: what needs the parent (provider cancellations awaiting a refund choice, spots that opened, applications to confirm, unfinished bookings, tasks on a provider's site, missing child or form details, unconfirmed external plans, conflicts and email questions), upcoming days off without cover grouped by break (state idea, with activity ids, when saved or followed options are available), recent updates, registration openings, waitlist positions and the next booked session. Dismissed items are left out. Ranking and counts are not part of the contract.","responses":{"200":{"description":"Home.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyPlanHome"}}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Read Home","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/home\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/home/dismissals/{itemId}":{"post":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Dismiss a Home item","description":"Hides a Home item for good, or until a future time. Dismissing again replaces the earlier dismissal. An update is dismissed as \"update:<id>\".","parameters":[{"name":"itemId","in":"path","required":true,"description":"The item's id.","schema":{"type":"string","pattern":"^[a-z_]+:[A-Za-z0-9-]{1,80}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"until":{"type":"string","format":"date-time","description":"Omitted hides the item for good."}}}}}},"responses":{"200":{"description":"The item is hidden.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["dismissed"],"properties":{"dismissed":{"const":true}}}}},"example":{"success":true,"data":{"dismissed":true}}}}},"400":{"description":"The item id or until is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"until must be a future RFC 3339 time.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Ask again in three days","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/home/dismissals/confirm_registration:$BOOKING_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"until\": \"2026-10-19T18:00:00Z\"}'"}]},"delete":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Restore a Home item","description":"Shows a dismissed item again. Restoring an item that is not dismissed succeeds.","parameters":[{"name":"itemId","in":"path","required":true,"description":"The item's id.","schema":{"type":"string","pattern":"^[a-z_]+:[A-Za-z0-9-]{1,80}$"}}],"responses":{"200":{"description":"The item shows again.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["dismissed"],"properties":{"dismissed":{"const":false}}}}},"example":{"success":true,"data":{"dismissed":false}}}}},"400":{"description":"The item id is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"itemId must look like <kind>:<subjectId>.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Undo a dismissal","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/home/dismissals/confirm_registration:$BOOKING_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/home/updates/{updateId}/undo":{"post":{"tags":["Internal family calendar"],"x-internal":true,"summary":"Undo an update","description":"Reverts what an update applied, such as school days or a plan learned from email, and marks it undone, in one transaction.","parameters":[{"name":"updateId","in":"path","required":true,"description":"The update's id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The update, now undone.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["update"],"properties":{"update":{"$ref":"#/components/schemas/FamilyPlanHomeUpdate"}}}}}}}},"404":{"description":"No such update of the viewer's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"Update not found.","data":null}}}},"409":{"description":"The update was already undone or cannot be undone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyPlanError"},"example":{"error":true,"message":"This update was already undone.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not add or change plans (write_plans; a caregiver), or the plan is another adult's and the viewer is not the owner"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Undo an update","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/home/updates/$UPDATE_ID/undo\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/bookings/{bookingId}/change":{"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Change a booking","description":"Moves a booked booking to another open session of the same activity. It starts a new, linked booking with the same child, answers and shared fields (no waitlist); the original stays booked and is cancelled, and its payment refunded by `refundMethod`, only once the new one is booked. If the new one gets no place the original is untouched. Staff cancel the original registration with the camp. `warnings` contains `overlap` when the new dates overlap another booking of the child.","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyBookingChangeInput"}}}},"responses":{"200":{"description":"The existing new booking for this `idempotencyKey` (a replay).","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["booking","replaces","warnings"],"properties":{"booking":{"$ref":"#/components/schemas/FamilyBookingDetail"},"replaces":{"type":"string","format":"uuid"},"warnings":{"type":"array","items":{"type":"string"}}}}}}}}},"202":{"description":"The new booking.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["booking","replaces","warnings"],"properties":{"booking":{"$ref":"#/components/schemas/FamilyBookingDetail"},"replaces":{"type":"string","format":"uuid"},"warnings":{"type":"array","items":{"type":"string","enum":["overlap"],"x-open-enum":true}}}}}},"example":{"success":true,"data":{"booking":{"id":"9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","phase":"in_progress","status":"accepted","paymentStatus":"held","childProfileId":"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93","organization":{"id":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Lakeside Science Camp"},"activity":{"id":"7d3b9f60-2a4c-4e8b-9f1d-6c5a0e2b8d17","name":"Summer Science Camp"},"session":{"id":"d4b8f2a1-7c3e-4f9d-8a6b-1e2f3c4d5a6b","name":"Robotics Week 2","startDate":"2027-07-19","endDate":"2027-07-23","startTime":"09:00","endTime":"15:00","dates":[],"dayTimes":[]},"approval":{"approvedAt":"2026-10-09T17:00:00Z","maxSpend":{"amountCents":45000,"currency":"USD"},"allowWaitlist":false,"autoAcceptDecisions":false,"sharedFields":["child.birthDate"]},"progressStage":null,"hold":null,"decision":null,"decisions":[],"receipt":null,"failure":null,"checkingWith":null,"pollAfterMs":2000,"createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T17:06:41Z","events":[{"kind":"approval_granted","status":"authorizing","actor":"parent","summary":"Approved the booking with a spending limit of 450.00 USD.","createdAt":"2026-10-09T17:00:00Z"}],"waitlist":null,"canceledBy":null,"refund":null,"dueOnProviderSite":null,"note":null,"askAgainAfter":null,"replacesBookingId":"6f1d2c3b-4a5e-4f60-8b7a-1c2d3e4f5a6b","replacedByBookingId":null,"payment":{"paymentMethod":"default","creditsCents":0,"cardHoldCents":45500,"serviceFeeCents":500},"cancellation":{"allowed":true,"action":"cancel","reason":null,"deadline":null,"refundableCents":0,"methods":[]},"bookedBy":null},"replaces":"6f1d2c3b-4a5e-4f60-8b7a-1c2d3e4f5a6b","warnings":[]}}}}},"400":{"description":"An unknown property or an invalid id or amount.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"sessionId must be a UUID.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"$ref":"#/components/responses/BookingNotFound"},"409":{"description":"The booking is not booked, is already being changed, or the profile can no longer be used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Only a booked booking can be changed.","data":null}}}},"422":{"description":"The session is in another activity, is the same session, or is waitlist-only or closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Choose another open session of the same activity.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not book (book; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Change a booking","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/bookings/$BOOKING_ID/change\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"idempotencyKey\": \"3e9a1c7b-5d2f-4b8e-a6c4-0f1e2d3c4b5a\", \"sessionId\": \"d4b8f2a1-7c3e-4f9d-8a6b-1e2f3c4d5a6b\", \"refundMethod\": \"credits\"}'"}]}},"/api/users/viewer/bookings/{bookingId}/outcome":{"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Report a booking outcome","description":"Records the parent’s answer about what happened on the camp’s side. Each answer is an event with actor `parent`.","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyBookingOutcomeInput"}}}},"responses":{"200":{"description":"The updated booking.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["booking"],"properties":{"booking":{"$ref":"#/components/schemas/FamilyBookingDetail"}}}}}}}},"400":{"description":"Unknown property or outcome.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"outcome must be registered, not_registered, not_yet or provider_task_done.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"$ref":"#/components/responses/BookingNotFound"},"409":{"description":"The outcome does not apply to the booking now: the first three need an applied booking, `provider_task_done` needs `dueOnProviderSite`; or the booking is briefly busy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Only an application waiting for the camp's answer can be marked registered.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not book (book; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Report a booking outcome","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/bookings/$BOOKING_ID/outcome\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"outcome\": \"registered\"}'"}]}},"/api/sessions/{sessionId}/booking-form":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Get a session's booking form","description":"Returns what an approval for the session shares of the child's profile, for the checklist before Book. Send `shareFields` from it with the approval.","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Get a session's booking form","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/sessions/$SESSION_ID/booking-form\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"responses":{"200":{"description":"The booking form.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["form"],"properties":{"form":{"$ref":"#/components/schemas/FamilyBookingForm"}}}}},"example":{"success":true,"data":{"form":{"sessionId":"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48","alwaysShared":["child.firstName","child.lastName","guardian.email","guardian.name"],"shareFields":["child.allergies","child.birthDate","child.grade","guardian.phone"]}}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"description":"No such live session, or bookings are off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Session not found.","data":null}}}},"403":{"description":"The role may not book (book; a caregiver)"}}}},"/api/users/viewer/wallet":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Get the wallet","description":"Returns the wallet balance: credits the family added or was refunded, less what is spent or held for bookings.","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["wallet"],"properties":{"wallet":{"$ref":"#/components/schemas/FamilyWallet"}}}}},"example":{"success":true,"data":{"wallet":{"balanceCents":2500,"currency":"USD"}}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not see money (read_money; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Get the wallet","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/wallet\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/provider-accounts":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"List provider accounts","description":"Lists the provider accounts Hallpass created for the signed-in adult and keeps for their later bookings, such as a CourseStorm account made during a booking. Each shows its site and masked username, never a password. They are the adult's own, whatever their family role.","responses":{"200":{"description":"The adult's provider accounts.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["providerAccounts"],"properties":{"providerAccounts":{"type":"array","items":{"$ref":"#/components/schemas/FamilyProviderAccount"}}}}}},"example":{"success":true,"data":{"providerAccounts":[{"platform":"coursestorm","provider":"CourseStorm","site":"https://coursestorm.com/","usernameDisplay":"r…@example.com","createdAt":"2026-11-14T17:02:11Z","lastUsedAt":null}]}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"$ref":"#/components/responses/BookingsDisabled"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"List provider accounts","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/provider-accounts\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/provider-accounts/{platform}":{"delete":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Remove a provider account","description":"Erases the adult's provider account for a platform from School's Out. The account itself stays at the provider; a later booking there creates a new one, or stops when one already exists for the adult's email.","parameters":[{"name":"platform","in":"path","required":true,"schema":{"type":"string","enum":["coursestorm"]}}],"responses":{"200":{"description":"The adult's remaining provider accounts.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["providerAccounts"],"properties":{"providerAccounts":{"type":"array","items":{"$ref":"#/components/schemas/FamilyProviderAccount"}}}}}},"example":{"success":true,"data":{"providerAccounts":[{"platform":"coursestorm","provider":"CourseStorm","site":"https://coursestorm.com/","usernameDisplay":"r…@example.com","createdAt":"2026-11-14T17:02:11Z","lastUsedAt":null}]}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"description":"Bookings are off, or the adult has no account for that platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Provider account not found.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Remove a provider account","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/provider-accounts/coursestorm\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/wallet/entries":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"List wallet entries","description":"Returns wallet history newest first, a page at a time. These entries also back the Payments history.","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["top_up","refund","booking_payment","booking_hold","hold_released","adjustment","gift_card","referral","group_rate"],"x-open-enum":true},"description":"Only entries of this kind."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, at most 100."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"`nextCursor` from the previous page."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["entries","nextCursor"],"properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/FamilyWalletEntry"}},"nextCursor":{"type":["string","null"]}}}}},"example":{"success":true,"data":{"entries":[{"id":"1042","kind":"top_up","label":"Credits added","detail":null,"amountCents":2500,"method":{"brand":"visa","last4":"4242"},"bookingId":null,"occurredAt":"2026-10-16T09:00:00Z"}],"nextCursor":null}}}}},"400":{"description":"Unknown kind, or an invalid limit or cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"kind is not a wallet entry kind.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not see money (read_money; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"List wallet entries","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/wallet/entries\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/payments":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"List payments","description":"Returns the family’s payments newest first, a page at a time, for Settings › Payments: each ledger transaction that moved the family’s money once, with what it took from or returned to the card and the wallet. Booking payments, refunds, wallet top-ups, gift card purchases and redemptions, referral credits and group rate credits are listed; credits held for a booking are not payments until the booking is paid.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, at most 100."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"`nextCursor` from the previous page."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["payments","nextCursor"],"properties":{"payments":{"type":"array","items":{"$ref":"#/components/schemas/FamilyPayment"}},"nextCursor":{"type":["string","null"]}}}}},"example":{"success":true,"data":{"payments":[{"id":"7f1c2e4a-5b6d-4c8e-9f0a-1b2c3d4e5f60","kind":"booking_payment","label":"Paid · STEAM Discovery Camp","detail":"Experiment PDX · Maya","cardCents":-36500,"walletCents":-6000,"currency":"USD","card":null,"serviceFeeCents":500,"bookingId":"0b6a1f9e-3c2d-4e5f-8a7b-9c0d1e2f3a4b","giftCardId":null,"occurredAt":"2026-10-16T09:00:00Z"},{"id":"2a3b4c5d-6e7f-4809-8a1b-2c3d4e5f6a7b","kind":"top_up","label":"Credits added","detail":null,"cardCents":-10000,"walletCents":10000,"currency":"USD","card":{"brand":"visa","last4":"4242"},"serviceFeeCents":null,"bookingId":null,"giftCardId":null,"occurredAt":"2026-10-15T18:30:00Z"}],"nextCursor":null}}}}},"400":{"description":"An invalid limit or cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"cursor is not a cursor this route returned.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not see money (read_money; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"List payments","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/payments\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/payments/{paymentId}/receipt":{"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Email a payment's receipt","description":"Emails a fresh copy of one of the family’s payments’ receipts to the receipt email (else the account email), whatever kind of payment it is and whether automatic receipts are on. Receipts are School’s Out’s own: the camp’s organization and address, the child, the session dates, the camp price, service fee and credits applied, how it was paid, and the references. Every booking payment, refund, top-up and gift card purchase is also emailed its receipt when it is posted, unless the family turned `receipts` off in its notification preferences. The email is queued in the outbox and sent by the worker.","parameters":[{"name":"paymentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"A payment’s `id` from GET /api/users/viewer/payments."}],"responses":{"202":{"description":"Queued.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["sentTo"],"properties":{"sentTo":{"type":"string","format":"email","description":"Where the receipt goes."}}}}},"example":{"success":true,"data":{"sentTo":"sam@example.com"}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"description":"Not one of the family’s payments.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Payment not found.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"This deployment cannot seal emails (no API_AES_KEY).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"We can’t email receipts right now. Try again later.","data":null}}}},"403":{"description":"The role may not see money (read_money; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Email a payment's receipt","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/payments/7f1c2e4a-5b6d-4c8e-9f0a-1b2c3d4e5f60/receipt\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/admin/family-bookings/follow-ups":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"List bookings waiting on staff","description":"For a staff or ADMIN person on their own account (an ADMIN machine key, a service call or a preview gets 403): bookings whose card refund or capture Stripe refused, most recently changed first. The system does not retry these; a refused refund stays `pending` for the parent until staff move the money and resolve the booking with `PATCH /api/admin/family-bookings/{bookingId}/follow-up`, which takes it off this list. The first page (no cursor) also lists, as `charges`, the wallet top-ups and gift card purchases closed as unresolved because Stripe never named their PaymentIntent in time (newest first, at most `limit`); bookings whose camp deferred payment are listed with reason `payment_deferred` until staff release or capture their kept hold.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, at most 100."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"`nextCursor` from the previous page."}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["followUps","charges","nextCursor"],"properties":{"followUps":{"type":"array","items":{"$ref":"#/components/schemas/FamilyBookingStaffFollowUp"}},"charges":{"type":"array","items":{"$ref":"#/components/schemas/FamilyStaffChargeFollowUp"},"description":"Only on the first page; empty on later pages."},"nextCursor":{"type":["string","null"]}}}}},"example":{"success":true,"data":{"followUps":[{"bookingId":"0b6a1f9e-3c2d-4e5f-8a7b-9c0d1e2f3a4b","ownerUserId":"4d2c1b0a-9e8f-4a7b-8c6d-5e4f3a2b1c0d","reason":"card_refund_refused","status":"cancelled","paymentStatus":"captured","paymentIntentId":"pi_3QxExample","refund":{"method":"card","amountCents":42500,"walletCents":0,"status":"pending"},"cardPaidCents":42500,"currency":"USD","organizationName":"Experiment PDX","sessionName":"STEAM Discovery Camp","updatedAt":"2026-10-16T09:00:00Z"}],"nextCursor":null,"charges":[]}}}}},"400":{"description":"An invalid limit or cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"cursor is not a cursor this route returned.","data":null}}}},"401":{"description":"Sign-in required."},"403":{"description":"The viewer is not ADMIN or staff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"An ADMIN or staff user is required.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"ADMIN or staff accounts only (cookie or account credential).","x-curl-examples":[{"title":"List bookings waiting on staff","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/admin/family-bookings/follow-ups\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/admin/family-bookings/{bookingId}/follow-up":{"patch":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Resolve a booking waiting on staff","description":"For a staff or ADMIN person on their own account (an ADMIN machine key, a service call or a preview gets 403, because the `reconciled` event names the person): records how staff settled a booking's money by hand and takes it off the follow-up list. A `card_refund_refused` booking is resolved as `refunded`: staff refunded the card part through Stripe, so the parent's refund becomes `done`, the ledger records the card refund as the automatic refund would, and the parent is notified. A `card_capture_refused` booking is resolved as `collected` or `written_off`; either way any wallet credits still held for it go back to the wallet. A `payment_deferred` booking is resolved as `hold_released` (nothing is owed to the camp: only the booking's service fee is charged, and the rest of its card hold and held credits go back) or `hold_captured` with `amountCents`, what School's Out paid the camp (at most the spending limit): the payment and the service fee are recorded and charged like a capture, held credits first, then the card hold. A `card_capture_refused` booking's collected or written-off amount includes its service fee. Each resolution is recorded as a staff `reconciled` event with the note.","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["resolution","note"],"properties":{"resolution":{"type":"string","enum":["refunded","collected","written_off","hold_released","hold_captured"],"description":"`refunded` answers `card_refund_refused`; `collected` and `written_off` answer `card_capture_refused`; `hold_released` and `hold_captured` answer `payment_deferred`."},"note":{"type":"string","minLength":1,"maxLength":1000,"description":"What staff did, kept on the booking's staff event. Surrounding whitespace is trimmed."},"amountCents":{"type":"integer","minimum":1,"description":"With `hold_captured` only: what School's Out paid the camp, in the spending limit's currency, at most the limit."}}},"example":{"resolution":"refunded","note":"Refunded $425.00 in the Stripe dashboard, re_3QxExample."}}}},"responses":{"200":{"description":"The recorded resolution.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["resolved"],"properties":{"resolved":{"$ref":"#/components/schemas/FamilyBookingStaffResolution"}}}}},"example":{"success":true,"data":{"resolved":{"bookingId":"0b6a1f9e-3c2d-4e5f-8a7b-9c0d1e2f3a4b","reason":"card_refund_refused","resolution":"refunded","note":"Refunded $425.00 in the Stripe dashboard, re_3QxExample."}}}}}},"400":{"description":"Unknown property, an unknown resolution, a note that is empty or longer than 1000 characters, or amountCents missing from `hold_captured` or sent with another resolution.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"note must be 1 to 1000 characters.","data":null}}}},"401":{"description":"Sign-in required."},"403":{"description":"The viewer is not ADMIN or staff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"An ADMIN or staff user is required.","data":null}}}},"404":{"description":"No such booking, or bookings are disabled (BOOKINGS_ENABLED is not true).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Booking not found.","data":null}}}},"409":{"description":"Nothing on the booking waits on staff (already resolved), another update holds the booking (retry), or Stripe refused to capture a deferred payment's hold (it may have expired; resolve it as `hold_released` instead).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Nothing on this booking waits on staff.","data":null}}}},"422":{"description":"The resolution does not answer the booking's follow-up reason, or amountCents exceeds the spending limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"A card_capture_refused follow-up cannot be resolved as refunded.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Stripe did not confirm releasing or capturing a deferred payment's hold; nothing was recorded. Send the same resolution again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Stripe did not confirm. Send the same resolution again.","data":null}}}}},"security":[{"AccountCredential":[]}],"x-auth":"ADMIN or staff accounts only (cookie or account credential). Cookie mutations require CSRF. The route is registered only when the API runs with BOOKINGS_ENABLED=true; otherwise it returns 404.","x-curl-examples":[{"title":"Resolve a booking waiting on staff","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/admin/family-bookings/0b6a1f9e-3c2d-4e5f-8a7b-9c0d1e2f3a4b/follow-up\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"resolution\": \"refunded\", \"note\": \"Refunded $425.00 in the Stripe dashboard, re_3QxExample.\"}'"}]}},"/api/admin/family-bookings/charges/{kind}/{chargeId}/follow-up":{"patch":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Resolve an unresolved saved-card charge","description":"For a staff or ADMIN person on their own account (an ADMIN machine key, a service call or a preview gets 403, because the resolution names the person): records how staff closed a wallet top-up or gift card purchase listed under `charges` on the follow-up list, after looking it up in Stripe by its `stripeIdempotencyKey`, and takes it off the list. `refunded`: Stripe charged the card and staff refunded it there. `written_off`: staff closed it without moving money. `resolved_in_stripe`: staff settled it in Stripe by hand (for example Stripe never charged the card, or the PaymentIntent was cancelled there). The route moves no money and posts nothing: an unresolved charge never credited the wallet or activated a gift card, and stays unresolved for the parent (its idempotency key keeps answering 409). The body has the booking follow-up's shape.","parameters":[{"name":"kind","in":"path","required":true,"schema":{"type":"string","enum":["top_up","gift_card"]},"description":"The charge's `kind` from the follow-up list."},{"name":"chargeId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The charge's `id` from the follow-up list."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["resolution","note"],"properties":{"resolution":{"type":"string","enum":["refunded","written_off","resolved_in_stripe"]},"note":{"type":"string","minLength":1,"maxLength":1000,"description":"What staff found and did in Stripe, kept on the charge. Surrounding whitespace is trimmed."}}},"example":{"resolution":"refunded","note":"Charged once (pi_3QxExample); refunded in the Stripe dashboard."}}}},"responses":{"200":{"description":"The recorded resolution.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["resolved"],"properties":{"resolved":{"$ref":"#/components/schemas/FamilyStaffChargeResolution"}}}}},"example":{"success":true,"data":{"resolved":{"kind":"top_up","id":"5d1c2b3a-4e5f-4a6b-8c7d-9e0f1a2b3c4d","resolution":"refunded","note":"Charged once (pi_3QxExample); refunded in the Stripe dashboard."}}}}}},"400":{"description":"Unknown property (amountCents included), a resolution other than refunded, written_off or resolved_in_stripe, or a note that is empty or longer than 1000 characters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"resolution must be refunded, written_off or resolved_in_stripe, without amountCents.","data":null}}}},"401":{"description":"Sign-in required."},"403":{"description":"The viewer is not ADMIN or staff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"An ADMIN or staff user is required.","data":null}}}},"404":{"description":"No unresolved charge of that kind and id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Charge not found.","data":null}}}},"409":{"description":"Staff already resolved this charge.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Staff already resolved this charge.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"ADMIN or staff accounts only (cookie or account credential). Cookie mutations require CSRF.","x-curl-examples":[{"title":"Resolve an unresolved saved-card charge","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/admin/family-bookings/charges/top_up/5d1c2b3a-4e5f-4a6b-8c7d-9e0f1a2b3c4d/follow-up\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"resolution\": \"refunded\", \"note\": \"Charged once (pi_3QxExample); refunded in the Stripe dashboard.\"}'"}]}},"/api/users/viewer/wallet/top-ups":{"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Add credits","description":"Charges a saved card now and adds the amount to the wallet once the payment succeeds. Nothing is added for a payment that needs authentication or is declined (`402`) or whose outcome is unknown (`503`; resend the same request).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyWalletTopUpInput"}}}},"responses":{"200":{"description":"The same top-up, already completed (a replay).","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["wallet","entry"],"properties":{"wallet":{"$ref":"#/components/schemas/FamilyWallet"},"entry":{"$ref":"#/components/schemas/FamilyWalletEntry"}}}}}}}},"201":{"description":"The top-up succeeded and its credits are in the wallet.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["wallet","entry"],"properties":{"wallet":{"$ref":"#/components/schemas/FamilyWallet"},"entry":{"$ref":"#/components/schemas/FamilyWalletEntry"}}}}},"example":{"success":true,"data":{"wallet":{"balanceCents":2500,"currency":"USD"},"entry":{"id":"1042","kind":"top_up","label":"Credits added","detail":null,"amountCents":2500,"method":{"brand":"visa","last4":"4242"},"bookingId":null,"occurredAt":"2026-10-16T09:00:00Z"}}}}}},"400":{"description":"Unknown property, or an invalid key, amount or card choice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"amountCents must be between 1000 and 50000.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"402":{"description":"The card needs the parent (`requiresAction.clientSecret`, confirm it with Stripe.js, then resend the same request) or was declined (`declined.code`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BookingError"},{"type":"object","properties":{"requiresAction":{"type":"object","required":["clientSecret"],"properties":{"clientSecret":{"type":"string"}}},"declined":{"type":"object","required":["code"],"properties":{"code":{"type":"string"}}}}}]},"example":{"error":true,"message":"Your card was declined. Try another card.","data":null,"declined":{"code":"insufficient_funds"}}}}},"409":{"description":"No such saved card, or the key was reused with different details. A charge whose PaymentIntent Stripe never named within its idempotency window is closed as unresolved for staff, and its key answers 409 from then on: use a new idempotencyKey.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Add a card first.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The payment outcome is unknown; resend the same request. Also on a server without a Stripe key (`STRIPE_SECRET_KEY`), before anything is read or recorded: “Payments aren't configured on this server.”","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"The payment could not be confirmed. Send the same request again.","data":null}}}},"403":{"description":"Only the family's owner manages cards, top-ups and gift cards (manage_cards)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Add credits","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/wallet/top-ups\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"idempotencyKey\": \"6c5b4a39-2817-4f6e-9d5c-4b3a29180f7e\", \"amountCents\": 2500, \"paymentMethod\": \"default\"}'"}]}},"/api/users/viewer/payment-method/{paymentMethodId}":{"delete":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Remove a card","description":"Removes a saved card and detaches it in Stripe: the default (the backup is promoted), the backup, or any other card in the response's `cards`, which leaves both slots as they are. A booking whose hold is on the card keeps its money held: the hold first moves to the parent's remaining card (the other slot, or the default for a card in neither; a new hold of the same amount there, then the old one is cancelled), and the booking's events record the move. Without another card, or while a hold on the card is still being placed, the card cannot be removed. When Stripe does not confirm the detach at once the card is still removed, and School's Out finishes the detach in the background, so a removed card never stays attached.","parameters":[{"name":"paymentMethodId","in":"path","required":true,"schema":{"type":"string","pattern":"^pm_"}}],"responses":{"200":{"description":"The saved cards after the change.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyPaymentMethods"}}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"description":"Not one of the parent’s saved cards.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Card not found.","data":null}}}},"409":{"description":"A booking's hold is on the card and there is no other card to move it to, the other card declined the moved hold (the card stays), or a hold on the card is still being placed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"A booking is holding money on this card. Add another card before removing it.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Stripe's answer to moving a booking's hold was unknown, or Stripe could not be asked whether a card outside the slots is the parent's; the card stays. Send the same request again. Also on a server without a Stripe key (`STRIPE_SECRET_KEY`), before anything is read or recorded: “Payments aren't configured on this server.”","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"The card could not be removed. Try again.","data":null}}}},"403":{"description":"Only the family's owner manages cards, top-ups and gift cards (manage_cards)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Remove a card","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/payment-method/$PAYMENT_METHOD_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/gift-cards":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"List gift cards you bought","description":"The viewer's purchased gift cards, newest first (at most 100). Purchases whose payment did not succeed are not listed. Lists show only each code's last four characters; GET /api/users/viewer/gift-cards/{giftCardId}/code shows a code.","responses":{"200":{"description":"The viewer's gift cards.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["giftCards"],"properties":{"giftCards":{"type":"array","items":{"$ref":"#/components/schemas/FamilyGiftCard"}}}}}},"example":{"success":true,"data":{"giftCards":[{"id":"5b0f8d2e-3c41-4b8a-9e6f-2d7a1c9b4e30","amountCents":5000,"currency":"USD","status":"active","codeLast4":"Q7XM","recipientName":"Jordan","recipientEmail":"jordan@example.com","emailStatus":"pending","message":"Happy summer!","card":{"brand":"visa","last4":"4242"},"purchasedAt":"2026-10-16T17:04:11Z","redeemedAt":null}]}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"Only the family's owner manages cards, top-ups and gift cards (manage_cards)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"List gift cards","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/gift-cards\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Buy a gift card","description":"Charges the viewer's saved card and, once the payment succeeds, returns the gift card with its code. The code is stored sealed, so a replay of the same request, or GET /api/users/viewer/gift-cards/{giftCardId}/code, shows it again, even after it is redeemed. With `recipientEmail`, School's Out also emails the code to the recipient in the background (see `emailStatus`); otherwise the purchaser shares it. Gift cards do not expire. Without the stored-secrets key the API refuses every purchase with 503 before any charge.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyGiftCardPurchaseInput"},"example":{"idempotencyKey":"0d6f1c9a-2b7e-4f3d-8a1c-5e9b7d2f4a60","amountCents":5000,"recipientName":"Jordan","recipientEmail":"jordan@example.com","message":"Happy summer!","paymentMethod":"default"}}}},"responses":{"201":{"description":"Paid. The response carries the code.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyGiftCardIssued"}}},"example":{"success":true,"data":{"giftCard":{"id":"5b0f8d2e-3c41-4b8a-9e6f-2d7a1c9b4e30","amountCents":5000,"currency":"USD","status":"active","codeLast4":"Q7XM","recipientName":"Jordan","recipientEmail":"jordan@example.com","emailStatus":"pending","message":"Happy summer!","card":{"brand":"visa","last4":"4242"},"purchasedAt":"2026-10-16T17:04:11Z","redeemedAt":null},"code":"K7MD-3QPA-WX9H-Q7XM"}}}}},"200":{"description":"A replay of a key whose card is already sold (active or redeemed): the same card and code, without a new charge.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyGiftCardIssued"}}},"example":{"success":true,"data":{"giftCard":{"id":"5b0f8d2e-3c41-4b8a-9e6f-2d7a1c9b4e30","amountCents":5000,"currency":"USD","status":"active","codeLast4":"Q7XM","recipientName":"Jordan","recipientEmail":"jordan@example.com","emailStatus":"pending","message":"Happy summer!","card":{"brand":"visa","last4":"4242"},"purchasedAt":"2026-10-16T17:04:11Z","redeemedAt":null},"code":"K7MD-3QPA-WX9H-Q7XM"}}}}},"400":{"description":"The body isn't a valid purchase: unknown property, bad key, amount out of bounds, too-long text or a bad email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"amountCents must be between 1000 and 50000.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"402":{"description":"The card needs the parent's confirmation (`requiresAction.clientSecret`: confirm it with Stripe.js, then replay the same request) or was declined (`declined.code`, Stripe's decline code; use a new idempotencyKey to try another card). Same shape as wallet top-ups.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BookingError"},{"type":"object","oneOf":[{"type":"object","required":["requiresAction"],"properties":{"requiresAction":{"type":"object","additionalProperties":false,"required":["clientSecret"],"properties":{"clientSecret":{"type":"string"}}}}},{"type":"object","required":["declined"],"properties":{"declined":{"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string"}}}}}]}]},"examples":{"requiresAction":{"value":{"error":true,"message":"Your bank needs you to confirm this payment.","data":null,"requiresAction":{"clientSecret":"pi_3Q..._secret_..."}}},"declined":{"value":{"error":true,"message":"Your card was declined. Try another card.","data":null,"declined":{"code":"insufficient_funds"}}}}}}},"409":{"description":"The chosen saved card slot is empty, the key was used with different details, or another request with this key is completing it. A charge whose PaymentIntent Stripe never named within its idempotency window is closed as unresolved for staff, and its key answers 409 from then on: use a new idempotencyKey.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"This purchase was already sent with different details. Use a new idempotencyKey.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Stripe's answer is unknown (nothing is charged twice; retry with the same key), or gift cards are unavailable because the API runs without its stored-secrets key (nothing was charged). Also on a server without a Stripe key (`STRIPE_SECRET_KEY`), before anything is read or recorded: “Payments aren't configured on this server.”","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"The payment could not be confirmed. Send the same request again.","data":null}}}},"403":{"description":"Only the family's owner manages cards, top-ups and gift cards (manage_cards)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Buy a $50 gift card","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/gift-cards\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"idempotencyKey\":\"0d6f1c9a-2b7e-4f3d-8a1c-5e9b7d2f4a60\",\"amountCents\":5000,\"recipientName\":\"Jordan\"}'"}]}},"/api/users/viewer/gift-cards/{giftCardId}/code":{"get":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Show a gift card's code","description":"Shows the code of a gift card the viewer bought, active or redeemed, so the purchaser can share it again. Only the purchaser sees it: another account's card, or a purchase that was never paid, is not found. The answer is never cached (Cache-Control: private, no-store).","parameters":[{"name":"giftCardId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The gift card and its code.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyGiftCardIssued"}}},"example":{"success":true,"data":{"giftCard":{"id":"5b0f8d2e-3c41-4b8a-9e6f-2d7a1c9b4e30","amountCents":5000,"currency":"USD","status":"active","codeLast4":"Q7XM","recipientName":"Jordan","recipientEmail":"jordan@example.com","emailStatus":"pending","message":"Happy summer!","card":{"brand":"visa","last4":"4242"},"purchasedAt":"2026-10-16T17:04:11Z","redeemedAt":null},"code":"K7MD-3QPA-WX9H-Q7XM"}}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"description":"No gift card of the viewer's with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Gift card not found.","data":null}}}},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Gift card codes are unavailable: the API runs without its stored-secrets key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Gift cards are unavailable right now. Try again later.","data":null}}}},"403":{"description":"Only the family's owner manages cards, top-ups and gift cards (manage_cards)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Show a gift card's code","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/gift-cards/5b0f8d2e-3c41-4b8a-9e6f-2d7a1c9b4e30/code\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/wallet/redeem":{"post":{"tags":["Internal family bookings"],"x-internal":true,"summary":"Redeem a gift card","description":"Adds a gift card's value to the viewer's wallet. A code can be redeemed once, by any account. Unknown codes count against a per-account limit; past it the route answers 429 until the window ends.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyGiftCardRedeemInput"},"example":{"code":"k7md-3qpa-wx9h-q7xm"}}}},"responses":{"201":{"description":"Redeemed. The wallet's new balance and the credit entry.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["wallet","entry"],"properties":{"wallet":{"$ref":"#/components/schemas/FamilyWallet"},"entry":{"$ref":"#/components/schemas/FamilyWalletEntry"}}}}},"example":{"success":true,"data":{"wallet":{"balanceCents":5000,"currency":"USD"},"entry":{"id":"9182","kind":"gift_card","label":"Gift card redeemed","detail":null,"amountCents":5000,"method":null,"bookingId":null,"occurredAt":"2026-10-16T18:20:03Z"}}}}}},"400":{"description":"The body isn't exactly `{ code }`, or the code is not 16 characters of the gift card alphabet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"Enter the 16-character code from the gift card email.","data":null}}}},"401":{"description":"Current account required. A request without a session or credential answers {\"authorized\": false}; a machine key or delegated service request answers the error envelope with message \"unauthorized.\", because these routes act only as the signed-in parent."},"404":{"description":"No gift card has this code. Counts against the limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"That gift card code was not recognized.","data":null}}}},"409":{"description":"The gift card was already redeemed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingError"},"example":{"error":true,"message":"This gift card was already redeemed.","data":null}}}},"429":{"description":"Too many unrecognized codes from this account. `Retry-After` gives the seconds to wait. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BookingError"},{"type":"object","required":["retryAfterMs"],"properties":{"retryAfterMs":{"type":"integer","minimum":0}}}]},"example":{"error":true,"message":"Too many gift card codes were not recognized. Try again later.","data":null,"retryAfterMs":1800000}}}},"403":{"description":"Only the family's owner manages cards, top-ups and gift cards (manage_cards)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Redeem a gift card","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/wallet/redeem\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"K7MD-3QPA-WX9H-Q7XM\"}'"}]}},"/api/activities":{"get":{"description":"Filters, sorts, and pages canonical activities. Requires Starter. A consumer's own signed-in account (the cookie with a live session, or the key with x-session-id; no household or child needed) reads at any tier with the family fields added to its tier's: below Starter only the consumer-safe fields (FamilyCatalogRecord); from Starter up every field its tier reads plus the family fields. Family reads add the family filters, distanceMiles with near, derived availability (the best of nextSessions), and nextSessions, limit=0 counts and fields=pins. A family list row (the family projection, without fields=pins or showRemoved) reads its media in the list statement: image_urls holds at most its first photo (selected cover, else first gallery photo), or is empty; every other read keeps every photo. A family activity row's organization summary adds logo_url, its camp's selected logo, when it has one. Rows omit bookkeeping, history, and scores. Family filters are indexed: radiusMiles through the configuration location index with an exact spheroid check, dates, from/to, weekends, timeOfDay and dayLength through each session's stored filter values (schedule bounds, attended dates, covered weekdays, clocks), priceMax through basis and amount indexes, program and care through GIN indexes on their stored buckets, interest through positive interest answers confirmed per candidate, and openSpots through an index on whether a session could have places. Legacy parameters and filter JSON combine with AND before exact filtered counts and pagination. Filters use this entity own disclosed fields; Ordinary permitted JSON records include resolved_cover with the ultimate source entity, inheritance flag, and structured stock attribution. Owned gallery and image compatibility fields retain their existing meaning. The family projection refuses filter JSON (400).","parameters":[{"description":"Case-insensitive substring search over visible text fields. Repeat up to five times to match any term. Activity matching also includes the linked organization's visible name, within the same eligibility and preview disclosure scope.","in":"query","name":"q","required":false,"schema":{"type":"string","maxLength":200}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): words that must each match whole. Every word of every value (values split at whitespace) must be in the record's family search fields or its linked organization's visible name (the search fields a family sees, as a family sees them, so hidden text never matches) as a whole word or a word of the same English stem, case-insensitively (art matches Arts but not party; camps matches camp); a value's words may match in different fields. Stop words (the, for) are ignored, so a value of stop words alone keeps every row. ANDed with contains, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters; at most 10 words in all. For partial words, send contains.","in":"query","name":"text","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): text that each must contain. Each value matches when any visible search field contains it anywhere, case-insensitively, as one q value does (art matches Arts and party), as the reader sees the field, so hidden text never matches. ANDed with each other, with text, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters. Very common fragments of a few letters are the slowest searches. Activity matching also includes the linked organization's visible name, within the same eligibility and preview disclosure scope.","in":"query","name":"contains","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Case-insensitive exact city name or its lowercase hyphenated slug.","in":"query","name":"city","required":false,"schema":{"type":"string"}},{"description":"Case-insensitive exact region, such as a state abbreviation.","in":"query","name":"region","required":false,"schema":{"type":"string"}},{"description":"Exact category recorded on the record itself.","in":"query","name":"category","required":false,"schema":{"type":"string"}},{"description":"Records whose inclusive age_range_years contains this whole age.","in":"query","name":"ageYears","required":false,"schema":{"type":"integer","minimum":0}},{"description":"Legacy month-based age filter.","in":"query","name":"ageMonths","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000}},{"description":"Records whose grade range includes this grade; -1 for pre-kindergarten.","in":"query","name":"grade","required":false,"schema":{"type":"integer","minimum":-1,"maximum":12}},{"description":"Records of this organization.","in":"query","name":"organizationId","required":false,"schema":{"type":"string","format":"uuid"}},{"description":"Inclusive minimum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response.","in":"query","name":"minimumScore","required":false,"schema":{"type":"integer","minimum":0,"maximum":100}},{"description":"Inclusive maximum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response. Must not be below minimumScore.","in":"query","name":"maximumScore","required":false,"schema":{"type":"integer","minimum":0,"maximum":100}},{"description":"Sort direction.","in":"query","name":"order","required":false,"schema":{"type":"string","enum":["asc","desc"]}},{"description":"One-based page number. A missing or invalid page reads as 1, and a page past the last reachable one (whose offset is within 10,000 rows) reads as the last reachable page.","in":"query","name":"page","required":false,"schema":{"type":"integer","minimum":1}},{"description":"Page size from 1 to 100 (500 with fields=pins). In the family projection, limit=0 returns total without rows. Read as the legacy explorer read it: an absent, non-numeric or non-positive limit reads as the default, a fraction is floored (below 1 answers no rows), and a larger value reads as the maximum.","in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":0,"maximum":500}},{"description":"Set false to return counts and metadata with an empty row array.","in":"query","name":"includeRows","required":false,"schema":{"type":"boolean"}},{"description":"Set false to omit resolved_cover and skip its read, for a reader that shows the row's own photo (image_urls). Defaults to true; must be exactly true or false, given once.","in":"query","name":"includeCover","required":false,"schema":{"default":true,"type":"boolean"}},{"description":"Exact skill level.","in":"query","name":"skillLevel","required":false,"schema":{"type":"string"}},{"description":"Exact format.","in":"query","name":"format","required":false,"schema":{"type":"string"}},{"description":"Exact seasonality.","in":"query","name":"seasonality","required":false,"schema":{"type":"string"}},{"description":"Sessions with an attended date on or after this date, or a date_range ending on or after it or stating no end (an ongoing session). On activities (family projection), an activity with such a session.","in":"query","name":"from","required":false,"schema":{"type":"string","format":"date"}},{"description":"Sessions with an attended date on or before this date, or a date_range starting on or before it. On activities (family projection), an activity with such a session.","in":"query","name":"to","required":false,"schema":{"type":"string","format":"date"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated YYYY-MM-DD dates, at most 31: a session attending any of them or whose date_range contains one (a date_range without an end runs on).","in":"query","name":"dates","required":false,"schema":{"type":"string","example":"2027-11-26,2027-11-27"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. true keeps sessions whose schedule covers a Saturday or Sunday: an attended weekend date (explicit dates or a weekday rule), or a date_range whose days include one. A range of a week or more, or one with a start and no end, always matches; a range without a start never does.","in":"query","name":"weekends","required":false,"schema":{"type":"boolean"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated morning, afternoon, evening, by the session's earliest start: before 12:00, before 17:00, then later.","in":"query","name":"timeOfDay","required":false,"schema":{"type":"string","example":"morning,afternoon"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated half_day, full_day, overnight. half_day and full_day compare the longest same-day span of non-overnight clocks; overnight is the session's explicit overnight attribute.","in":"query","name":"dayLength","required":false,"schema":{"type":"string","example":"full_day"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated before, after; every listed care must be offered (by the session's schedule or configuration, or for an activity by itself or any of its sessions). A configuration offers care when its availability is available (or a truthy value stored before normalization) or it gives the care clock.","in":"query","name":"care","required":false,"schema":{"type":"string","example":"before,after"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole US dollars. Keeps sessions whose sole stated price (session_prices) is at most priceMax: at priceBasis when it is given, else at whatever basis the price states, so a cap reads \"up to $N, whatever the period\". A price without a stated price_basis counts as session (the whole enrollment). A session with several quotes states no sole price and never matches; an activity matches through any of its live sessions. Other currencies are excluded.","in":"query","name":"priceMax","required":false,"schema":{"type":"integer","minimum":0}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. The basis priceMax compares at; requires priceMax. Without it, priceMax compares each price at the basis it states.","in":"query","name":"priceBasis","required":false,"schema":{"type":"string","enum":["hour","day","week","month","session"]}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. true keeps sessions whose availability is open or limited on the household-local today and that have not ended (a session without a stated end is ongoing).","in":"query","name":"openSpots","required":false,"schema":{"type":"boolean"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated whole years, each 0 to 17 (children of different ages): keeps records whose age_range_years contains any of them. With ageMin or ageMax, a record that fits any listed age or overlaps that range matches. A session that states no ages of its own is read with its activity's.","in":"query","name":"ages","required":false,"schema":{"type":"string","example":"4,11"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole years; keeps records whose age_range_years overlaps [ageMin, ageMax] (either end open), or fits one of ages when that is given too; a session that states no ages of its own is read with its activity's.","in":"query","name":"ageMin","required":false,"schema":{"type":"integer","minimum":0,"maximum":17}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole years; see ageMin.","in":"query","name":"ageMax","required":false,"schema":{"type":"integer","minimum":0,"maximum":17}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated camps, classes, after_school, mapped onto the activity's format and seasonality.","in":"query","name":"program","required":false,"schema":{"type":"string","example":"camps"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated feed interest ids (at most 20): offerings tagged with any of them.","in":"query","name":"interest","required":false,"schema":{"type":"string","example":"robotics,painting"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Origin for distanceMiles, radiusMiles and orderBy=distance: home (the household's searchArea.point; 409 when it is unset; 400 for the consumer site key) or latitude,longitude in decimal degrees.","in":"query","name":"near","required":false,"schema":{"type":"string","example":"47.61,-122.33"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Keeps rows whose own coordinates are within this many miles of near; rows without coordinates are excluded. Requires near.","in":"query","name":"radiusMiles","required":false,"schema":{"type":"integer","minimum":1,"maximum":50}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. A map's view: west,south,east,north in decimal degrees (west greater than east crosses the antimeridian; south below north). Keeps rows whose point (their own, else the one they inherit) is street-grade (coordinate_precision exact, building, parcel or street) and inside the view; a ZIP or state centre is never in view. Combines with every other filter, near, orderBy=distance and fields=pins; counts (limit=0) count the rows in view. 400 when it is not four finite numbers describing a nonempty view.","in":"query","name":"bbox","required":false,"schema":{"type":"string","example":"-122.75,45.45,-122.55,45.6"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. pins alone returns only {id, latitude, longitude, name} for rows with coordinates, with limit up to 500. Otherwise a comma-separated list of the costly list fields to add to each row: familiesBooked, priceTier, contentUpdatedAt (a detail always carries them; a list row carries one only when named here).","in":"query","name":"fields","required":false,"schema":{"type":"string","pattern":"^(pins|(familiesBooked|priceTier|contentUpdatedAt)(,(familiesBooked|priceTier|contentUpdatedAt))*)$","example":"familiesBooked"}},{"description":"Sort column. In the family projection, distance (with near) orders nearest first by great-circle distance, rows without coordinates last (distanceMiles is the spheroid distance, so rows within about 0.5% of each other may list in either order); other family orderings are name, plus start_date, end_date, registration_open_date and city on sessions. In the family projection, next_session_start orders activities by their soonest next session (one under way, or without a stated start, counts as today; the session nextSessions lists first), activities without one last, then by name and id; the response echoes orderBy: next_session_start. It is 400 on other kinds.","in":"query","name":"orderBy","required":false,"schema":{"type":"string"}},{"description":"Consumer site key only; any other credential gets 400 (a signed-in family reads in its household time zone). The visitor's IANA time zone (UTC or an Area/Location tz database name), which sets the day availability, nextSessions and date filters read from. Without it, signed-out reads use the launch metro's zone (America/Los_Angeles). An unknown or process-dependent name (Local) is 400.","in":"query","name":"tz","required":false,"schema":{"type":"string","example":"America/Los_Angeles"}},{"name":"filter","in":"query","required":false,"description":"One URL-encoded JSON predicate tree, sent once. Combines with all legacy filters using AND before rows, totals and facet counts are selected. Read /api/canonical/schema tables[*].filterCapabilities for visible fields, operators, relations and limits. Invalid/unknown/over-complex filters return 400; unavailable fields return 403, including in nonmatching branches. Do not broaden a failed request by dropping predicates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalFilter"}}}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"name":"includeResolution","in":"query","required":false,"description":"Include field_resolution on each returned entity row: own, inherited, or unset for each projected field. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeSources","in":"query","required":false,"description":"Include field_sources on each returned entity row, mapping fields to the canonical entity supplying their effective value or configuration unit. Independent of includeResolution; defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeOwnValues","in":"query","required":false,"description":"Include own_values on each returned entity row, retaining null local values. Requires editor authorization; otherwise returns 403. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated format kinds (day_camp, overnight_camp, class, drop_in, event): activities whose formatKind is any of them (a session through its activity), read from activity_format_facts. An unknown value is 400.","in":"query","name":"formatKind","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page. The tier projection returns tier-masked records with lockedColumns; the family projection adds projection: family, returns FamilyCatalogRecord rows (or FamilyCatalogPin rows with fields=pins) and an empty lockedColumns. Success envelope with data.activities, total, unfilteredTotal, page, pageCount, limit, orderBy, order, columns, tier and sourceClass. total is the matched root count before pagination; includeRows=false leaves the array empty.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"object","properties":{"activities":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/CanonicalDetailRow"},{"$ref":"#/components/schemas/FamilyCatalogRecord"},{"$ref":"#/components/schemas/FamilyCatalogPin"}]}},"total":{"type":"integer"},"unfilteredTotal":{"type":"integer"},"page":{"type":"integer"},"pageCount":{"description":"The pages a client can read: ceil(total/limit), capped at the deepest page the list serves (offset 10000, page 10000/limit+1), so paging up to pageCount always ends. null with limit=0.","type":["integer","null"]},"limit":{"type":"integer"},"orderBy":{"type":"string"},"order":{"type":"string"},"tier":{"type":"string"},"projection":{"const":"family"}}}}}}}},"400":{"$ref":"#/components/responses/InvalidDatasetQuery"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Free accounts without a family profile: the Data Explorer opens with Starter. A filter field or relationship is unavailable under current field disclosure."},"409":{"description":"near=home when the family's household has neither a searchArea.point nor a known searchArea.postalCode (a co-parent's or caregiver's home is the family owner's)."},"500":{"$ref":"#/components/responses/ServerError"},"429":{"description":"Request allowance exceeded, with Retry-After. A data-API key's hourly read allotment answers retryAfterMs, readsAllotment and windowMs. A signed-in family session or the consumer site key answers its per-minute app limit with retryAfterMs and windowMs only (the FamilyRequestLimit shape)."},"402":{"$ref":"#/components/responses/PlanRequired"}},"summary":"Query activities","tags":["Canonical"],"x-auth":"Account or named integration API key on Starter or above, or a consumer's own signed-in account (the cookie with a live session, or the key with x-session-id) at any tier, from sign-up on, whose read adds the family fields to its tier's (below Starter, the family fields alone). Machine keys, service delegation and previews read exactly their tier. The parent site's catalog-only consumer site key (SCHOOLS_OUT_CATALOG_API_KEY) also reads it for signed-out visitors, as a Free family with no household: the family fields only, near as latitude,longitude (near=home is 400), and the day in the visitor's tz (default: the launch metro's zone).","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/activities\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"near=home\" \\\n  --data-urlencode \"radiusMiles=10\" \\\n  --data-urlencode \"weekends=true\"","title":"Family: activities near home on weekends"},{"title":"Find activities under a typed filter","code":"curl --get \"https://apis.schoolsoutapp.com/api/activities\" -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" --data-urlencode 'filter={\"field\":\"age_range_years\",\"op\":\"contains\",\"value\":10}'"}]}},"/api/sessions":{"get":{"description":"Filters, sorts, and pages canonical sessions. Requires Starter. A consumer's own signed-in account (the cookie with a live session, or the key with x-session-id; no household or child needed) reads at any tier with the family fields added to its tier's: below Starter only the consumer-safe fields (FamilyCatalogRecord); from Starter up every field its tier reads plus the family fields. Family reads add the family filters, distanceMiles with near, derived availability, limit=0 counts and fields=pins. Rows omit bookkeeping, history, and scores. Family filters are indexed: radiusMiles through the configuration location index with an exact spheroid check, dates, from/to, weekends, timeOfDay and dayLength through each session's stored filter values (schedule bounds, attended dates, covered weekdays, clocks), priceMax through basis and amount indexes, program and care through GIN indexes on their stored buckets, interest through positive interest answers confirmed per candidate, and openSpots through an index on whether a session could have places. Legacy parameters and filter JSON combine with AND before exact filtered counts and pagination. Filters use this entity own disclosed fields; Ordinary permitted JSON records include resolved_cover with the ultimate source entity, inheritance flag, and structured stock attribution. Owned gallery and image compatibility fields retain their existing meaning. The family projection refuses filter JSON (400).","parameters":[{"description":"Case-insensitive substring search over visible text fields. Repeat up to five times to match any term.","in":"query","name":"q","required":false,"schema":{"type":"string","maxLength":200}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): words that must each match whole. Every word of every value (values split at whitespace) must be in the record's family search fields (the search fields a family sees, as a family sees them, so hidden text never matches) as a whole word or a word of the same English stem, case-insensitively (art matches Arts but not party; camps matches camp); a value's words may match in different fields. Stop words (the, for) are ignored, so a value of stop words alone keeps every row. ANDed with contains, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters; at most 10 words in all. For partial words, send contains.","in":"query","name":"text","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Repeatable (at most 5): text that each must contain. Each value matches when any visible search field contains it anywhere, case-insensitively, as one q value does (art matches Arts and party), as the reader sees the field, so hidden text never matches. ANDed with each other, with text, with the q group (whose values stay ORed) and with every other filter. Each value is trimmed, non-empty, at most 200 characters. Very common fragments of a few letters are the slowest searches.","in":"query","name":"contains","required":false,"schema":{"type":"string"},"style":"form","explode":true},{"description":"Case-insensitive exact city name or its lowercase hyphenated slug.","in":"query","name":"city","required":false,"schema":{"type":"string"}},{"description":"Case-insensitive exact region, such as a state abbreviation.","in":"query","name":"region","required":false,"schema":{"type":"string"}},{"description":"Exact category recorded on the record itself.","in":"query","name":"category","required":false,"schema":{"type":"string"}},{"description":"Records whose inclusive age_range_years contains this whole age.","in":"query","name":"ageYears","required":false,"schema":{"type":"integer","minimum":0}},{"description":"Legacy month-based age filter.","in":"query","name":"ageMonths","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000}},{"description":"Records whose grade range includes this grade; -1 for pre-kindergarten.","in":"query","name":"grade","required":false,"schema":{"type":"integer","minimum":-1,"maximum":12}},{"description":"Records of this organization.","in":"query","name":"organizationId","required":false,"schema":{"type":"string","format":"uuid"}},{"description":"Inclusive minimum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response.","in":"query","name":"minimumScore","required":false,"schema":{"type":"integer","minimum":0,"maximum":100}},{"description":"Inclusive maximum whole-number score from this entity’s persisted ranking snapshot. Available to family readers at every tier and the consumer site credential. Applied before pagination and facet aggregation; records without a cached score do not match. Does not add score fields to the response. Must not be below minimumScore.","in":"query","name":"maximumScore","required":false,"schema":{"type":"integer","minimum":0,"maximum":100}},{"description":"Sort direction.","in":"query","name":"order","required":false,"schema":{"type":"string","enum":["asc","desc"]}},{"description":"Set true to place past sessions after current and undated sessions before pagination, retaining orderBy/order and stable ID ties within each group. A session is past only when every disclosed schedule configuration has a known final date before today in UTC; a session ending today remains current. Missing or restricted schedules are not past. Defaults to false.","in":"query","name":"pastLast","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"One-based page number. A missing or invalid page reads as 1, and a page past the last reachable one (whose offset is within 10,000 rows) reads as the last reachable page.","in":"query","name":"page","required":false,"schema":{"type":"integer","minimum":1}},{"description":"Page size from 1 to 100 (500 with fields=pins). In the family projection, limit=0 returns total without rows. Read as the legacy explorer read it: an absent, non-numeric or non-positive limit reads as the default, a fraction is floored (below 1 answers no rows), and a larger value reads as the maximum.","in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":0,"maximum":500}},{"description":"Set false to return counts and metadata with an empty row array.","in":"query","name":"includeRows","required":false,"schema":{"type":"boolean"}},{"description":"Set false to omit resolved_cover and skip its read, for a reader that shows the row's own photo (image_urls). Defaults to true; must be exactly true or false, given once.","in":"query","name":"includeCover","required":false,"schema":{"default":true,"type":"boolean"}},{"description":"Sessions of this activity.","in":"query","name":"activityId","required":false,"schema":{"type":"string","format":"uuid"}},{"description":"Registration status, where the projection shows it: open, waitlist or closed (the stored enum), or unknown for a session whose status is unknown (NULL). Any other value, or the parameter given twice, is a 400.","in":"query","name":"registrationStatus","required":false,"schema":{"type":"string","enum":["open","waitlist","closed","unknown"]}},{"description":"Sessions with an attended date on or after this date, or a date_range ending on or after it or stating no end (an ongoing session). On activities (family projection), an activity with such a session.","in":"query","name":"from","required":false,"schema":{"type":"string","format":"date"}},{"description":"Sessions with an attended date on or before this date, or a date_range starting on or before it. On activities (family projection), an activity with such a session.","in":"query","name":"to","required":false,"schema":{"type":"string","format":"date"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated YYYY-MM-DD dates, at most 31: a session attending any of them or whose date_range contains one (a date_range without an end runs on).","in":"query","name":"dates","required":false,"schema":{"type":"string","example":"2027-11-26,2027-11-27"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. true keeps sessions whose schedule covers a Saturday or Sunday: an attended weekend date (explicit dates or a weekday rule), or a date_range whose days include one. A range of a week or more, or one with a start and no end, always matches; a range without a start never does.","in":"query","name":"weekends","required":false,"schema":{"type":"boolean"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated morning, afternoon, evening, by the session's earliest start: before 12:00, before 17:00, then later.","in":"query","name":"timeOfDay","required":false,"schema":{"type":"string","example":"morning,afternoon"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated half_day, full_day, overnight. half_day and full_day compare the longest same-day span of non-overnight clocks; overnight is the session's explicit overnight attribute.","in":"query","name":"dayLength","required":false,"schema":{"type":"string","example":"full_day"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated before, after; every listed care must be offered (by the session's schedule or configuration, or for an activity by itself or any of its sessions). A configuration offers care when its availability is available (or a truthy value stored before normalization) or it gives the care clock.","in":"query","name":"care","required":false,"schema":{"type":"string","example":"before,after"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole US dollars. Keeps sessions whose sole stated price (session_prices) is at most priceMax: at priceBasis when it is given, else at whatever basis the price states, so a cap reads \"up to $N, whatever the period\". A price without a stated price_basis counts as session (the whole enrollment). A session with several quotes states no sole price and never matches; an activity matches through any of its live sessions. Other currencies are excluded.","in":"query","name":"priceMax","required":false,"schema":{"type":"integer","minimum":0}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. The basis priceMax compares at; requires priceMax. Without it, priceMax compares each price at the basis it states.","in":"query","name":"priceBasis","required":false,"schema":{"type":"string","enum":["hour","day","week","month","session"]}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. true keeps sessions whose availability is open or limited on the household-local today and that have not ended (a session without a stated end is ongoing).","in":"query","name":"openSpots","required":false,"schema":{"type":"boolean"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated whole years, each 0 to 17 (children of different ages): keeps records whose age_range_years contains any of them. With ageMin or ageMax, a record that fits any listed age or overlaps that range matches. A session that states no ages of its own is read with its activity's.","in":"query","name":"ages","required":false,"schema":{"type":"string","example":"4,11"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole years; keeps records whose age_range_years overlaps [ageMin, ageMax] (either end open), or fits one of ages when that is given too; a session that states no ages of its own is read with its activity's.","in":"query","name":"ageMin","required":false,"schema":{"type":"integer","minimum":0,"maximum":17}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Whole years; see ageMin.","in":"query","name":"ageMax","required":false,"schema":{"type":"integer","minimum":0,"maximum":17}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated camps, classes, after_school, mapped onto the activity's format and seasonality.","in":"query","name":"program","required":false,"schema":{"type":"string","example":"camps"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated feed interest ids (at most 20): offerings tagged with any of them.","in":"query","name":"interest","required":false,"schema":{"type":"string","example":"robotics,painting"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Origin for distanceMiles, radiusMiles and orderBy=distance: home (the household's searchArea.point; 409 when it is unset; 400 for the consumer site key) or latitude,longitude in decimal degrees.","in":"query","name":"near","required":false,"schema":{"type":"string","example":"47.61,-122.33"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Keeps rows whose own coordinates are within this many miles of near; rows without coordinates are excluded. Requires near.","in":"query","name":"radiusMiles","required":false,"schema":{"type":"integer","minimum":1,"maximum":50}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. A map's view: west,south,east,north in decimal degrees (west greater than east crosses the antimeridian; south below north). Keeps rows whose point (their own, else the one they inherit) is street-grade (coordinate_precision exact, building, parcel or street) and inside the view; a ZIP or state centre is never in view. Combines with every other filter, near, orderBy=distance and fields=pins; counts (limit=0) count the rows in view. 400 when it is not four finite numbers describing a nonempty view.","in":"query","name":"bbox","required":false,"schema":{"type":"string","example":"-122.75,45.45,-122.55,45.6"}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. pins alone returns only {id, latitude, longitude, name} for rows with coordinates, with limit up to 500. Otherwise a comma-separated list of the costly list fields to add to each row: contentUpdatedAt (a detail always carries them; a list row carries one only when named here). familiesBooked and priceTier are for organizations and activities (400 here).","in":"query","name":"fields","required":false,"schema":{"type":"string","pattern":"^(pins|(contentUpdatedAt)(,(contentUpdatedAt))*)$","example":"contentUpdatedAt"}},{"description":"Sort column. In the family projection, distance (with near) orders nearest first by great-circle distance, rows without coordinates last (distanceMiles is the spheroid distance, so rows within about 0.5% of each other may list in either order); other family orderings are name, plus start_date, end_date, registration_open_date and city on sessions.","in":"query","name":"orderBy","required":false,"schema":{"type":"string"}},{"description":"Consumer site key only; any other credential gets 400 (a signed-in family reads in its household time zone). The visitor's IANA time zone (UTC or an Area/Location tz database name), which sets the day availability, nextSessions and date filters read from. Without it, signed-out reads use the launch metro's zone (America/Los_Angeles). An unknown or process-dependent name (Local) is 400.","in":"query","name":"tz","required":false,"schema":{"type":"string","example":"America/Los_Angeles"}},{"name":"filter","in":"query","required":false,"description":"One URL-encoded JSON predicate tree, sent once. Combines with all legacy filters using AND before rows, totals and facet counts are selected. Read /api/canonical/schema tables[*].filterCapabilities for visible fields, operators, relations and limits. Invalid/unknown/over-complex filters return 400; unavailable fields return 403, including in nonmatching branches. Do not broaden a failed request by dropping predicates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalFilter"}}}},{"in":"query","name":"showRemoved","description":"Include retained removed records and removal metadata. Defaults to false; true requires a live staff or ADMIN operator and is rejected for preview or non-operator viewers. Repeat parameters and non-boolean values are invalid. Removed records and descendants of removed parents are excluded by default.","schema":{"type":"boolean","default":false}},{"name":"includeResolution","in":"query","required":false,"description":"Include field_resolution on each returned entity row: own, inherited, or unset for each projected field. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeSources","in":"query","required":false,"description":"Include field_sources on each returned entity row, mapping fields to the canonical entity supplying their effective value or configuration unit. Independent of includeResolution; defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"name":"includeOwnValues","in":"query","required":false,"description":"Include own_values on each returned entity row, retaining null local values. Requires editor authorization; otherwise returns 403. Defaults to false. Repeated or non-boolean values return 400. Previews omit field metadata.","schema":{"type":"boolean","default":false}},{"description":"Family reads only (a consumer's own signed-in account at any tier, or the consumer site key); other reads ignore it. Comma-separated format kinds (day_camp, overnight_camp, class, drop_in, event): activities whose formatKind is any of them (a session through its activity), read from activity_format_facts. An unknown value is 400.","in":"query","name":"formatKind","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page. The tier projection returns tier-masked records with lockedColumns; the family projection adds projection: family, returns FamilyCatalogRecord rows (or FamilyCatalogPin rows with fields=pins) and an empty lockedColumns. Success envelope with data.sessions, total, unfilteredTotal, page, pageCount, limit, orderBy, order, columns, tier and sourceClass. total is the matched root count before pagination; includeRows=false leaves the array empty.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"object","properties":{"sessions":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/CanonicalDetailRow"},{"$ref":"#/components/schemas/FamilyCatalogRecord"},{"$ref":"#/components/schemas/FamilyCatalogPin"}]}},"total":{"type":"integer"},"unfilteredTotal":{"type":"integer"},"page":{"type":"integer"},"pageCount":{"description":"The pages a client can read: ceil(total/limit), capped at the deepest page the list serves (offset 10000, page 10000/limit+1), so paging up to pageCount always ends. null with limit=0.","type":["integer","null"]},"limit":{"type":"integer"},"orderBy":{"type":"string"},"order":{"type":"string"},"tier":{"type":"string"},"projection":{"const":"family"}}}}}}}},"400":{"$ref":"#/components/responses/InvalidDatasetQuery"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Free accounts without a family profile: the Data Explorer opens with Starter. A filter field or relationship is unavailable under current field disclosure."},"409":{"description":"near=home when the family's household has neither a searchArea.point nor a known searchArea.postalCode (a co-parent's or caregiver's home is the family owner's)."},"500":{"$ref":"#/components/responses/ServerError"},"429":{"description":"Request allowance exceeded, with Retry-After. A data-API key's hourly read allotment answers retryAfterMs, readsAllotment and windowMs. A signed-in family session or the consumer site key answers its per-minute app limit with retryAfterMs and windowMs only (the FamilyRequestLimit shape)."},"402":{"$ref":"#/components/responses/PlanRequired"}},"summary":"Query sessions","tags":["Canonical"],"x-auth":"Account or named integration API key on Starter or above, or a consumer's own signed-in account (the cookie with a live session, or the key with x-session-id) at any tier, from sign-up on, whose read adds the family fields to its tier's (below Starter, the family fields alone). Machine keys, service delegation and previews read exactly their tier. The parent site's catalog-only consumer site key (SCHOOLS_OUT_CATALOG_API_KEY) also reads it for signed-out visitors, as a Free family with no household: the family fields only, near as latitude,longitude (near=home is 400), and the day in the visitor's tz (default: the launch metro's zone).","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/sessions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" \\\n  --data-urlencode \"near=home\" \\\n  --data-urlencode \"radiusMiles=10\" \\\n  --data-urlencode \"weekends=true\"","title":"Family: sessions near home on weekends"},{"title":"Find sessions under a typed filter","code":"curl --get \"https://apis.schoolsoutapp.com/api/sessions\" -H \"x-api-key: $SCHOOLS_OUT_API_KEY\" --data-urlencode 'filter={\"field\":\"age_range_years\",\"op\":\"contains\",\"value\":10}'"}]}},"/api/users/viewer/feed":{"get":{"tags":["Internal family feed"],"x-internal":true,"summary":"Read the family feed","description":"Returns the current edition's day: edition is the current edition, and editions is the day's morning edition then its evening edition, both published on the day's first read, so the evening is out before it becomes current. It is the only day a family can read: an earlier one is replaced, not kept, and no other day is ever generated. The route takes no query parameters. Post bodies are paragraphs of runs; a camp run only ever names the post's own camp. note is never null: the adult's daily note, as GET /api/users/viewer/feed/note answers it.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyFeed"}}},"example":{"success":true,"data":{"edition":{"id":"7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f","date":"2026-10-19","slot":"morning","label":"Monday morning","publishedAt":"2026-10-19T14:00:03Z","posts":[{"id":"0d9c8b7a-6f5e-4d3c-2b1a-0f9e8d7c6b5a","kind":"camp","topic":"science","headline":"Registration is open for Junior Explorers","body":[[{"camp":{"organizationId":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Riverside Camps","logoUrl":null}},{"text":" opened registration. For ages 6 to 12. Next session: Mon, Oct 26 to Fri, Oct 30. About 2 miles away."}]],"image":{"url":"https://riverside.example/explorers.jpg","alt":"Junior Explorers","position":null},"until":"2026-10-26","reason":"Registration opened at Riverside Camps, which you follow.","signal":null,"organizationId":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","activityId":"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48"}]},"editions":[{"id":"7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f","date":"2026-10-19","slot":"morning","label":"Monday morning","publishedAt":"2026-10-19T14:00:03Z","posts":[{"id":"0d9c8b7a-6f5e-4d3c-2b1a-0f9e8d7c6b5a","kind":"camp","topic":"science","headline":"Registration is open for Junior Explorers","body":[[{"camp":{"organizationId":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Riverside Camps","logoUrl":null}},{"text":" opened registration. For ages 6 to 12. Next session: Mon, Oct 26 to Fri, Oct 30. About 2 miles away."}]],"image":{"url":"https://riverside.example/explorers.jpg","alt":"Junior Explorers","position":null},"until":"2026-10-26","reason":"Registration opened at Riverside Camps, which you follow.","signal":null,"organizationId":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","activityId":"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48"}]},{"id":"5b2a1c0d-9e8f-4a7b-8c6d-3e2f1a0b9c8d","date":"2026-10-19","slot":"evening","label":"Monday evening","publishedAt":"2026-10-19T14:00:05Z","posts":[]}],"note":{"date":"2026-10-19","id":"quote-001","type":"quote","greeting":"Good morning","text":"Why, sometimes I’ve believed as many as six impossible things before breakfast.","credit":"Lewis Carroll, Through the Looking-Glass","doodle":"library","pose":{"time":"morning","season":"autumn"},"seenAt":null,"savedAt":null}}}}}},"400":{"description":"date and slot do not name an edition"},"401":{"description":"Current account required"},"404":{"description":"No edition was published for that date and slot"},"409":{"description":"The household has no area to measure from"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Read the current edition","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/feed\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/feed/camp-views":{"post":{"tags":["Internal family feed"],"x-internal":true,"summary":"Record time on a camp page","description":"Records that the parent spent dwellSeconds on an organization's, activity's or session's page: evidence of interest the feed learns from.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"targetKind":{"type":"string","enum":["organization","activity","session"]},"targetId":{"type":"string","format":"uuid"},"dwellSeconds":{"type":"integer","minimum":0,"maximum":86400}},"required":["targetKind","targetId","dwellSeconds"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"recorded":{"const":true,"type":"boolean"}},"required":["recorded"]}}}}}},"400":{"description":"Invalid target or dwell time"},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Record a look at an activity","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/feed/camp-views\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"targetKind\":\"activity\",\"targetId\":\"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48\",\"dwellSeconds\":42}'"}]}},"/api/users/viewer/feed/instructions":{"get":{"tags":["Internal family feed"],"x-internal":true,"summary":"Read your feed instructions","description":"The signed-in adult's own feed instructions (null while they read the default feed) and the feed's default wording for them. Every role reads their own; nobody reads another adult's.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"required":["feedInstructions"],"properties":{"feedInstructions":{"$ref":"#/components/schemas/FamilyFeedInstructions"}}}}}}}},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Read your feed instructions","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/feed/instructions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"put":{"tags":["Internal family feed"],"x-internal":true,"summary":"Save your feed instructions","description":"Replaces the signed-in adult's own feed instructions, or with null removes them (Reset to default). Every role writes their own; no household version is needed, and no other adult's feed changes. The next edition follows them.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"required":["feedInstructions"],"properties":{"feedInstructions":{"$ref":"#/components/schemas/FamilyFeedInstructions"}}}}}}}},"400":{"description":"Not {instructions: string | null}, or the text is empty after trimming or longer than 4000 characters"},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Save your feed instructions","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/feed/instructions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\"instructions\": \"Soccer news and weather that changes plans, please.\"}'"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FamilyFeedInstructionsInput"}}}}}},"/api/users/viewer/feed/note":{"get":{"tags":["Internal family feed"],"x-internal":true,"summary":"Read the daily note","description":"Returns the adult's daily note for today in the family's time zone, picking it on the date's first read: a line staff wrote for the family's area and date, else a line from the pool of credited quotes (books first published before 1930) and true facts. The pool is gated by the part of the day, the season's dates (event windows such as Halloween or a clock change first), and the day's weather at home; a line is not shown again within 45 days; quotes and facts are drawn by equal weight; an adult with no children set up gets a welcome line; when nothing fits, the untagged line shown longest ago. Unlike the feed it needs no home area. The route takes no query parameters.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"note":{"$ref":"#/components/schemas/FamilyFeedNote"}},"required":["note"]}}},"example":{"success":true,"data":{"note":{"date":"2026-10-19","id":"quote-001","type":"quote","greeting":"Good morning","text":"Why, sometimes I’ve believed as many as six impossible things before breakfast.","credit":"Lewis Carroll, Through the Looking-Glass","doodle":"library","pose":{"time":"morning","season":"autumn"},"seenAt":null,"savedAt":null}}}}}},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Read the daily note","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/feed/note\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/feed/notes/{date}/seen":{"put":{"tags":["Internal family feed"],"x-internal":true,"summary":"Mark the daily note seen","description":"Records that the adult saw their daily note for date, so its first-view mark draws once per account. Any date the adult was given a note for can be marked. Idempotent: the first time is kept.","parameters":[{"name":"date","in":"path","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"seenAt":{"type":"string","format":"date-time"}},"required":["seenAt"]}}}}}},"400":{"description":"date is not YYYY-MM-DD"},"401":{"description":"Current account required"},"404":{"description":"The adult was given no note on that date"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Mark a note seen","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/feed/notes/2026-10-19/seen\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/feed/notes/{date}/saved":{"put":{"tags":["Internal family feed"],"x-internal":true,"summary":"Save the daily note to Notes","description":"Saves the adult's line for date to their Notes, which Collections lists under Notes. Idempotent: saving again keeps the first time.","parameters":[{"name":"date","in":"path","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"date":{"type":"string","format":"date"},"savedAt":{"type":["string","null"],"format":"date-time"}},"required":["date","savedAt"]}}},"example":{"success":true,"data":{"date":"2026-10-19","savedAt":"2026-10-19T15:04:05Z"}}}}},"400":{"description":"date is not YYYY-MM-DD"},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."},"404":{"description":"The adult was given no note on that date"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Save the day's note","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/feed/notes/2026-10-19/saved\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"delete":{"tags":["Internal family feed"],"x-internal":true,"summary":"Remove the daily note from Notes","description":"Removes the adult's line for date from their Notes. Idempotent: removing an unsaved line answers savedAt null.","parameters":[{"name":"date","in":"path","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"date":{"type":"string","format":"date"},"savedAt":{"type":["string","null"],"format":"date-time"}},"required":["date","savedAt"]}}},"example":{"success":true,"data":{"date":"2026-10-19","savedAt":null}}}}},"400":{"description":"date is not YYYY-MM-DD"},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."},"404":{"description":"The adult was given no note on that date"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Remove a saved note","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/feed/notes/2026-10-19/saved\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/feed/notes/saved":{"get":{"tags":["Internal family feed"],"x-internal":true,"summary":"List saved notes","description":"Returns the adult's Notes, the lines they saved, the most recently saved first, at most 200. The route takes no query parameters.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"notes":{"type":"array","items":{"$ref":"#/components/schemas/FamilyFeedSavedNote"}}},"required":["notes"]}}},"example":{"success":true,"data":{"notes":[{"date":"2026-10-19","id":"quote-001","type":"quote","text":"Why, sometimes I’ve believed as many as six impossible things before breakfast.","credit":"Lewis Carroll, Through the Looking-Glass","doodle":"library","savedAt":"2026-10-19T15:04:05Z"}]}}}}},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"List saved notes","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/feed/notes/saved\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/feed/posts/{postId}/signal":{"put":{"tags":["Internal family feed"],"x-internal":true,"summary":"Set a post's signal","description":"Sets, changes or (with null) clears the one signal a post carries; setting the current signal again or clearing a post without one succeeds unchanged, and only a post that is not the viewer's is 404. Like and dislike teach the feed about the post's camp and interests; dont_show_again resets its interests as of that moment.","parameters":[{"name":"postId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"signal":{"type":["string","null"],"enum":["like","dislike","dont_show_again",null]}},"required":["signal"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"postId":{"type":"string","format":"uuid"},"signal":{"type":["string","null"],"enum":["like","dislike","dont_show_again",null]}},"required":["postId","signal"]}}}}}},"400":{"description":"Invalid signal"},"401":{"description":"Current account required"},"404":{"description":"Post not found for this account"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Like a post","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/feed/posts/0d9c8b7a-6f5e-4d3c-2b1a-0f9e8d7c6b5a/signal\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"signal\":\"like\"}'"}]}},"/api/users/viewer/feed/posts/{postId}/done":{"put":{"tags":["Internal family feed"],"x-internal":true,"summary":"Mark a post done","description":"Marks a post done or not done in the viewer's To do; the feed's posts carry it as done. Marking a post as it already is succeeds unchanged, a post marked done again keeps its first time, and only a post that is not the viewer's is 404.","parameters":[{"name":"postId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"done":{"type":"boolean"}},"required":["done"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"postId":{"type":"string","format":"uuid"},"done":{"type":"boolean"}},"required":["postId","done"]}}}}}},"400":{"description":"done is missing or not a boolean"},"401":{"description":"Current account required"},"404":{"description":"Post not found for this account"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Mark a post done","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/feed/posts/0d9c8b7a-6f5e-4d3c-2b1a-0f9e8d7c6b5a/done\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"done\":true}'"}]}},"/api/feed/daily-notes":{"get":{"tags":["Internal feed administration"],"x-internal":true,"summary":"List daily notes","description":"Returns every daily note for ?date=, written by staff or by the worker's writing model. Only staff's notes reach families; the model's are kept for review.","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"notes":{"type":"array","items":{"$ref":"#/components/schemas/FeedDailyNote"}}},"required":["notes"]}}}}}},"400":{"description":"date is not YYYY-MM-DD"},"401":{"description":"Current account required"},"403":{"description":"Not staff"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current staff or ADMIN person using their own account cookie or account credential. Machine keys, investor previews and delegated service requests are rejected. Cookie mutations require CSRF from a trusted origin.","x-curl-examples":[{"title":"List a day's notes","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/feed/daily-notes?date=2026-10-19\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/feed/daily-notes/{datasetId}/{kind}/{code}/{date}":{"put":{"tags":["Internal feed administration"],"x-internal":true,"summary":"Write a daily note","description":"Writes staff's note for a geography area and date, replacing any note there, including one the worker wrote. An adult whose home lies in the area and who has not yet read that date's daily note gets this line as their note, from their most specific matching area; a note already read that date stays.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"string"}},{"name":"kind","in":"path","required":true,"schema":{"type":"string","enum":["state","zip","metro","county","city","neighborhood"]}},{"name":"code","in":"path","required":true,"schema":{"type":"string"}},{"name":"date","in":"path","required":true,"schema":{"type":"string","format":"date"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"text":{"type":"string","minLength":1,"maxLength":280}},"required":["text"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"note":{"$ref":"#/components/schemas/FeedDailyNote"}},"required":["note"]}}}}}},"400":{"description":"Not one line of 1 to 280 characters, or an invalid date"},"401":{"description":"Current account required"},"403":{"description":"Not staff"},"404":{"description":"No such geography area"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current staff or ADMIN person using their own account cookie or account credential. Machine keys, investor previews and delegated service requests are rejected. Cookie mutations require CSRF from a trusted origin.","x-curl-examples":[{"title":"Write the Portland metro note","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/feed/daily-notes/geo-2026/metro/38900/2026-10-19\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"text\":\"Rain all morning: indoor picks first today.\"}'"}]},"delete":{"tags":["Internal feed administration"],"x-internal":true,"summary":"Remove a daily note","description":"Removes the note for an area and date, whoever wrote it. The worker may write that day's note again.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"string"}},{"name":"kind","in":"path","required":true,"schema":{"type":"string","enum":["state","zip","metro","county","city","neighborhood"]}},{"name":"code","in":"path","required":true,"schema":{"type":"string"}},{"name":"date","in":"path","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"deleted":{"const":true,"type":"boolean"}},"required":["deleted"]}}}}}},"400":{"description":"Invalid date"},"401":{"description":"Current account required"},"403":{"description":"Not staff"},"404":{"description":"No note for that area and date"},"503":{"description":"The feed took longer than its request deadline; nothing was published. Retry after a short delay."}},"security":[{"AccountCredential":[]}],"x-auth":"Current staff or ADMIN person using their own account cookie or account credential. Machine keys, investor previews and delegated service requests are rejected. Cookie mutations require CSRF from a trusted origin.","x-curl-examples":[{"title":"Remove a note","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/feed/daily-notes/geo-2026/metro/38900/2026-10-19\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/notification-preferences":{"get":{"tags":["Internal family notifications"],"x-internal":true,"summary":"Read notification preferences","description":"Returns the family's email, push and SMS choices per row (bookings, waitlist, schedule, groups, friends, assistant), whether payment receipts are emailed (`receipts`, email only), its quiet hours and its product news switch. A family that never saved gets the defaults at version 0, and a saved record without a row or setting reads it at its default; the defaults are policy and may change.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"preferences":{"$ref":"#/components/schemas/FamilyNotificationPreferences"}},"required":["preferences"]}}}}}},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Read notification preferences","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/notification-preferences\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"put":{"tags":["Internal family notifications"],"x-internal":true,"summary":"Save notification preferences","description":"Replaces the family's preferences. Every channel of bookings, waitlist and schedule must be present; `groups`, `friends`, `assistant` and `receipts` may be left out, as may `quietHours` and `productNews`, each keeping its saved value, so a client that predates them never changes them. Any other key is 400. Send the current version (0 before the first save); a stale version returns 409. Turning SMS on for a row where the saved matrix has it off needs a verified phone: otherwise 409 with code phone-unverified. SMS already on is kept without one, and no text is sent until a phone is verified.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"version":{"type":"integer","minimum":0},"preferences":{"$ref":"#/components/schemas/FamilyNotificationPreferenceValues"}},"required":["version","preferences"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"preferences":{"$ref":"#/components/schemas/FamilyNotificationPreferences"}},"required":["preferences"]}}}}}},"400":{"description":"Invalid or incomplete preferences"},"401":{"description":"Current account required"},"409":{"description":"Stale version, or SMS turned on without a verified phone (code phone-unverified)"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Save preferences","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/notification-preferences\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\":0,\"preferences\":{\"channels\":{\"bookings\":{\"email\":true,\"push\":true,\"sms\":false},\"waitlist\":{\"email\":true,\"push\":true,\"sms\":false},\"schedule\":{\"email\":false,\"push\":true,\"sms\":false},\"groups\":{\"email\":false,\"push\":true,\"sms\":false},\"friends\":{\"email\":false,\"push\":true,\"sms\":false},\"assistant\":{\"email\":false,\"push\":true,\"sms\":false}},\"quietHours\":{\"on\":true,\"from\":21,\"to\":7},\"productNews\":false}}'"}]}},"/api/users/viewer/notifications":{"get":{"tags":["Internal family notifications"],"x-internal":true,"summary":"List notifications","description":"Returns the family's notifications newest first with its unread count. filter=bookings keeps booking and waitlist notifications; filter=unread keeps unread ones. The badge reads unreadCount with limit=1. Pass nextCursor back as cursor, with the same filter, for the next page.","parameters":[{"name":"filter","in":"query","required":false,"description":"Which notifications; all by default.","schema":{"type":"string","enum":["all","bookings","unread"]}},{"name":"limit","in":"query","required":false,"description":"Page size, 50 by default.","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"nextCursor from the previous page of the same filter.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"notifications":{"type":"array","items":{"$ref":"#/components/schemas/FamilyNotification"}},"unreadCount":{"type":"integer","minimum":0},"nextCursor":{"type":["string","null"]}},"required":["notifications","unreadCount","nextCursor"]}}}}}},"400":{"description":"Invalid filter, limit or cursor"},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"List unread notifications","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/notifications?filter=unread&limit=20\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/notifications/read":{"post":{"tags":["Internal family notifications"],"x-internal":true,"summary":"Mark notifications read","description":"Marks the named notifications read, or with {all: true, before} every unread notification created at or before that moment, and returns the unread count after. Ids that are not the family's own change nothing.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"ids":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string","format":"uuid"}}},"required":["ids"]},{"type":"object","additionalProperties":false,"properties":{"all":{"const":true,"type":"boolean"},"before":{"type":"string","format":"date-time"}},"required":["all","before"]}]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"unreadCount":{"type":"integer","minimum":0}},"required":["unreadCount"]}}}}}},"400":{"description":"Neither or both forms, or more than 100 ids"},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Mark everything read","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/notifications/read\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"all\":true,\"before\":\"2026-10-19T15:04:05Z\"}'"}]}},"/api/users/viewer/push-subscriptions":{"put":{"tags":["Internal family notifications"],"x-internal":true,"summary":"Register a browser or app for push","description":"Subscribes the browser or native app of the caller's sign-in session to the family's push notifications. A browser sends PushSubscription.toJSON() (expirationTime is accepted and not kept); the app sends {kind: \"expo\", token, platform, deviceName?} with its Expo push token. A sign-in session has one browser subscription and one app token: subscribing again replaces it, keeping its id, and registering the same token again only refreshes it. An endpoint or token another session registered, of this family or another, moves to this one. Signing a session out (or revoking it) deletes its subscriptions. Push is sent only for types whose push preference is on. An app's notification carries data {notificationId, target} ({notificationId: uuid, target}), target being the notification's target as the inbox returns it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/FamilyPushSubscriptionInput"},{"$ref":"#/components/schemas/FamilyExpoPushSubscriptionInput"}]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"subscription":{"oneOf":[{"$ref":"#/components/schemas/FamilyPushSubscription"},{"$ref":"#/components/schemas/FamilyExpoPushSubscription"}]}},"required":["subscription"]}}}}}},"400":{"description":"Invalid endpoint, keys, token or platform"},"401":{"description":"Current account required, or its sign-in session has ended"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Register a browser","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/push-subscriptions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"endpoint\":\"https://fcm.googleapis.com/fcm/send/example\",\"keys\":{\"p256dh\":\"BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4\",\"auth\":\"BTBZMqHH6r4Tts7J_aSIgg\"}}'"},{"title":"Register the app","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/push-subscriptions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SCHOOLS_OUT_SESSION_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"kind\":\"expo\",\"token\":\"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]\",\"platform\":\"ios\",\"deviceName\":\"iPhone\"}'"}]},"delete":{"tags":["Internal family notifications"],"x-internal":true,"summary":"Unregister a browser or app from push","description":"Removes the family's own subscription at the endpoint, as the browser does when the parent turns push off there, or the app's own Expo push token ({token}), as the app does when push is turned off or before signing out (signing out also deletes it). An endpoint or token the family has not registered, including one another family holds, returns 404.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"properties":{"endpoint":{"type":"string","format":"uri","maxLength":2048,"description":"The browser's push service URL (https, at a known browser push service)."}},"required":["endpoint"]},{"type":"object","additionalProperties":false,"properties":{"token":{"type":"string","description":"The app's Expo push token."}},"required":["token"]}]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"deleted":{"const":true,"type":"boolean"}},"required":["deleted"]}}}}}},"400":{"description":"Neither one endpoint nor one token"},"401":{"description":"Current account required"},"404":{"description":"The family has no subscription at that endpoint or with that token"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Unregister a browser","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/push-subscriptions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"endpoint\":\"https://fcm.googleapis.com/fcm/send/example\"}'"},{"title":"Unregister the app","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/push-subscriptions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SCHOOLS_OUT_SESSION_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"token\":\"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]\"}'"}]}},"/api/users/viewer/push-subscriptions/vapid-key":{"get":{"tags":["Internal family notifications"],"x-internal":true,"summary":"Read the push public key","description":"Returns the VAPID public key (base64url, uncompressed P-256) to pass as applicationServerKey to PushManager.subscribe(). null when this deployment sends no push; the app then offers no push.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"properties":{"publicKey":{"type":["string","null"],"description":"base64url uncompressed P-256 public key; null when push is off."}},"required":["publicKey"]}}}}}},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Records are private to the signed-in adult.","x-curl-examples":[{"title":"Read the push public key","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/push-subscriptions/vapid-key\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/school-calendars/{sourceId}":{"put":{"tags":["Internal school calendars"],"x-internal":true,"summary":"Publish a school calendar","description":"Publishes a calendar source's days and term for every school it serves (schools whose calendar source it is), as the worker publishes an embedded calendar file: the same validation and one transaction that writes each entry's days and withdraws the source's days inside the calendar's span that it no longer lists. A single-day entry is written whatever its weekday; a multi-day entry covers each school's weekdays, and a multi-day no_school entry is a break. The span is the term, else the July-to-June school year of the earliest entry (of publishedOn when there are none), widened to every entry. Sending the same calendar again writes the same days and withdraws none. A source's publications run one at a time. Families' own overrides are untouched.","parameters":[{"in":"path","name":"sourceId","required":true,"schema":{"type":"string","format":"uuid"},"description":"A school_calendar feed source."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchoolCalendarPublicationInput"},"example":{"timeZone":"America/Los_Angeles","publishedOn":"2026-09-09","termStart":"2026-08-31","termEnd":"2027-06-11","entries":[{"type":"no_school","startsOn":"2026-11-11","endsOn":"2026-11-11","headline":"Veterans Day"},{"type":"no_school","startsOn":"2026-12-21","endsOn":"2027-01-01","headline":"Winter break"},{"type":"early_release","startsOn":"2026-10-21","endsOn":"2026-10-21","headline":"Early release","endsAt":"12:30"}]}}}},"responses":{"200":{"description":"What the publication changed.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/SchoolCalendarPublication"}}},"example":{"success":true,"data":{"schools":84,"days":2016,"withdrawn":0}}}}},"400":{"description":"An unknown or mistyped property, a missing required property (entries included: an empty calendar is sent as entries: []), a sourceId other than the path's, more than 500 entries, or a calendar that fails validation (time zone, dates, entry type, term, endsAt only on an early release)."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"A valid credential that is not the service credential acting for an ADMIN or staff account."},"404":{"description":"No school_calendar source with this id."},"409":{"description":"The worker publishes this source from its embedded calendar files; another publisher would be overwritten."}},"security":[{"ApiKeyAuth":[]}],"x-auth":"The service credential with x-viewer-id naming an ADMIN or staff account. Account credentials, cookies, machine keys and the bare service credential are rejected.","x-curl-examples":[{"title":"Publish a district's calendar","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/school-calendars/$SOURCE_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_SERVICE_API_KEY\" \\\n  -H \"x-viewer-id: $OPERATOR_USER_ID\" \\\n  -H \"content-type: application/json\" \\\n  -d @calendar.json"}]}},"/api/ingestion/crawler-campaigns":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"GET crawler campaigns","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. List or create durable mass-crawl jobs. Creation accepts location, nearby_locations, providers, confidence_threshold, follow_aggregators and a stable idempotency_key. Retries retain the original creation identity after settings edits. The complete search plan is scoped to explicit localities, including neighborhood searches for Portland, Oregon.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"x-curl-examples":[{"title":"GET crawler campaigns","code":"curl -X GET \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]},"post":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"POST crawler campaigns","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. List or create durable mass-crawl jobs. Creation accepts location, nearby_locations, providers, confidence_threshold, follow_aggregators and a stable idempotency_key. Retries retain the original creation identity after settings edits. The complete search plan is scoped to explicit localities, including neighborhood searches for Portland, Oregon.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"location":{"type":"string"},"idempotency_key":{"type":"string"},"providers":{"type":"array","items":{"type":"string"}},"nearby_locations":{"type":"array","items":{"type":"string"}}},"required":["location","idempotency_key"]}}}},"x-curl-examples":[{"title":"POST crawler campaigns","code":"curl -X POST \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"location\":\"Portland, Oregon\",\"idempotency_key\":\"portland-camps-2026\",\"providers\":[\"google\"]}' -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/{id}":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"GET crawler campaigns","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Read current-generation search, crawl, resolved Trident classification and unresolved counts. Successful historical crawls are reused. No request owns worker lifetime. Counts additionally include informational location_pending_urls, location_unresolved_urls and location_mismatch_urls; geography never excludes an eligible camp. Location counts describe actual historical records only; missing checks are not pending work. queued_urls excludes acquisition suppressed by saved site qualification or unrelated link expansion. site_qualified_urls and discovery_only_urls count those previously requested, unacquired URLs separately; all discovery records remain saved. Use include_counts=false for immediate navigation and request counts separately. Geography is historical metadata; no new location work is scheduled.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"include_counts","in":"query","schema":{"type":"boolean","default":true},"description":"False reads settings without computing frontier counts; counts fields are zero in this fast metadata response."}],"x-curl-examples":[{"title":"GET crawler campaigns","code":"curl -X GET \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/00000000-0000-4000-8000-000000000001\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/{id}/control":{"post":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"POST crawler campaigns","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Pause, resume, or rerun. Provider account failures automatically pause active mass-crawl jobs and expose campaign.provider_block with provider (jev, proxy, openai, anthropic or capmonster) and a safe code. Restore account access or funds before resume; resume clears the block and continues the same generation using saved progress. Rerun requires a paused/completed campaign and expected_generation, making uncertain retries idempotent. It retains every saved URL and discovery path. usage_limit_reached identifies subscription allowance exhaustion, distinct from insufficient_balance. Transient throttling is retried without pausing every job.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"action":{"type":"string","enum":["pause","resume","rerun"]},"expected_generation":{"type":"integer","minimum":1}},"required":["action"]}}}},"x-curl-examples":[{"title":"POST crawler campaigns","code":"curl -X POST \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/00000000-0000-4000-8000-000000000001/control\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"action\":\"pause\",\"expected_generation\":1}' -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/{id}/urls":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"GET crawler campaigns","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Read up to 100 saved URLs after an optional decimal after ID, with crawl status, mass-crawl assessment, and historical source metadata. crawl_skip_reason is site_qualified, discovery_only or null; it distinguishes preserved URLs that need no acquisition from the active crawl queue. Policy 9 classification_details.version=2 includes source_analysis: named organizations, individual offerings, publisher relationships, source types, availability, seasons, grounded citations and cross-page identity links, joint uncalibrated confidence, and eligibility review. Old policy records remain retained. evidence_status distinguishes available, not_saved, acquisition_failed and explicit unusable-evidence reasons; ever_classified distinguishes no historical evaluation; classification_error retains operational errors separately from source verdicts. Each URL includes nullable location_check for an actual historical check: status (pending/running/retry/completed), projected decision (pending/matched/mismatch/unresolved) and nullable saved assessment. Assessments retain decision, confidence, requested locations, reason, model, original model responses and grounded evidence (URL, exact excerpt, content_sha256, fetched_at). Compact responses include next_after (decimal source URL ID or null), the projected confidence_threshold, trident_status (qualified, irrelevant, unresolved), organization_count and offering_count. Follow next_after for another page of at most 100 results.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"after","in":"query","schema":{"type":"string"}},{"name":"compact","in":"query","schema":{"type":"boolean","default":false},"description":"Return bounded summaries without model response bodies, citations, source facts or confidence replay variants. Fetch URL details on demand."}],"x-curl-examples":[{"title":"GET crawler campaigns","code":"curl -X GET \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/00000000-0000-4000-8000-000000000001/urls\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/{id}/discoveries":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"GET crawler campaigns","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Read up to 100 discovery edges after an optional decimal after ID. Without url_id, results cover the selected campaign across its runs. With url_id, incoming edges cover every mass-crawl job that recorded the shared URL. campaign_id, campaign_location, generation and parent_url_id retain the path provenance. settings_revision links each new discovery to the immutable settings snapshot used by its search or expansion. Older discoveries have null when their original settings cannot be recovered; campaign_location uses the recorded location when known.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"after","in":"query","schema":{"type":"string"}},{"name":"url_id","in":"query","schema":{"type":"string"}}],"x-curl-examples":[{"title":"GET crawler campaigns","code":"curl -X GET \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/00000000-0000-4000-8000-000000000001/discoveries\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/{id}/searches":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"GET crawler campaigns","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Read up to 100 term/provider states after an optional decimal after ID, with attempts, pages fetched and completion reasons. Blocks remain retryable instead of exhausted.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"after","in":"query","schema":{"type":"string"}}],"x-curl-examples":[{"title":"GET crawler campaigns","code":"curl -X GET \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/00000000-0000-4000-8000-000000000001/searches\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/search-plan":{"post":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Preview a mass-crawl search plan","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Generate the full deduplicated term list and query/provider count without creating a job or contacting search providers. The response contains data.search_plan with terms, providers and search_count. Cookie requests require CSRF.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"location":{"type":"string","description":"Explicit search locality, for example Portland, Oregon."},"nearby_locations":{"type":"array","items":{"type":"string"},"maxItems":29,"description":"Additional explicit localities."},"providers":{"type":"array","minItems":1,"items":{"type":"string","enum":["google","bing","duckduckgo","brave","startpage","qwant"]},"description":"Search providers included in the plan."}},"required":["location","nearby_locations","providers"]}}}},"x-curl-examples":[{"title":"Preview a mass-crawl search plan","code":"curl -X POST \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/search-plan\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"location\":\"Portland, Oregon\",\"nearby_locations\":[],\"providers\":[\"google\"]}' -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/{id}/settings":{"post":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Update mass-crawl settings","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Apply complete live settings with revision conflict protection. Completed matching searches and saved URL evidence are retained. Paused jobs stay paused; changing the search plan can reopen completed jobs. Returns data.campaign; stale revisions return 409. Every accepted edit records an immutable timestamped settings snapshot. Location changes reuse fetched evidence and queue a geographic check for the new area; confidence changes reuse saved scores.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"location":{"type":"string","description":"Explicit search locality, for example Portland, Oregon."},"nearby_locations":{"type":"array","items":{"type":"string"},"maxItems":29,"description":"Additional explicit localities."},"providers":{"type":"array","minItems":1,"items":{"type":"string","enum":["google","bing","duckduckgo","brave","startpage","qwant"]},"description":"Search providers included in the plan."},"confidence_threshold":{"type":"number","minimum":0,"maximum":1,"description":"Acceptance threshold applied to retained classification evidence."},"follow_aggregators":{"type":"boolean","description":"Discover outgoing URLs from saved aggregator pages."},"expected_revision":{"type":"integer","minimum":1,"description":"The settings revision last read by this operator."}},"required":["location","nearby_locations","providers","confidence_threshold","follow_aggregators","expected_revision"]}}}},"x-curl-examples":[{"title":"Update mass-crawl settings","code":"curl -X POST \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/00000000-0000-4000-8000-000000000001/settings\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"location\":\"Portland, Oregon\",\"nearby_locations\":[],\"providers\":[\"google\"],\"confidence_threshold\":0.8,\"follow_aggregators\":true,\"expected_revision\":1}' -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/{id}/homepages":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Read vetted camp homepages","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Read deduplicated first-party camp homepages at the saved confidence threshold. Any location and season qualifies; geographic checks are retained historical metadata and never gate eligibility. Each includes the current assessment and source_url_id/source_url/historical_source for detail navigation. The default full response retains classification_details; compact=true omits it and paginates after deduplication. Compact responses include next_after (source URL ID or null), trident_status, organization_count and offering_count.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"compact","in":"query","schema":{"type":"boolean","default":false},"description":"Return bounded summaries without model response bodies, citations, source facts or confidence replay variants. Fetch URL details on demand."},{"name":"after","in":"query","schema":{"type":"string"},"description":"Exclusive source URL cursor for compact=true."}],"x-curl-examples":[{"title":"Read vetted camp homepages","code":"curl -X GET \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/00000000-0000-4000-8000-000000000001/homepages\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/{id}/search-plan":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Read the saved mass-crawl search plan","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Read all enabled current-generation terms, including completed searches, without truncation or regeneration. Returns data.search_plan with terms, providers, search_count and settings_revision from one database snapshot.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}}],"x-curl-examples":[{"title":"Read the saved mass-crawl search plan","code":"curl -X GET \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/00000000-0000-4000-8000-000000000001/search-plan\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/{id}/settings-history":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Read immutable mass-crawl settings history","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Returns up to 100 saved settings revisions in descending order. Pass before=<oldest revision> for the next page. Each entry identifies the run, timestamp, operator when known, reason and complete settings snapshot. A baseline entry marks the retained current state for jobs created before history existed; earlier settings are not invented.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","required":["history"],"properties":{"history":{"type":"array","items":{"type":"object","required":["revision","generation","recorded_at","actor_id","reason","settings"],"properties":{"revision":{"type":"integer","minimum":1},"generation":{"type":"integer","minimum":1},"recorded_at":{"type":"string","format":"date-time"},"actor_id":{"type":["string","null"],"format":"uuid"},"reason":{"type":"string","enum":["created","settings","control","baseline"]},"settings":{"type":"object","additionalProperties":false,"properties":{"location":{"type":"string","description":"Explicit search locality, for example Portland, Oregon."},"nearby_locations":{"type":"array","items":{"type":"string"},"maxItems":29,"description":"Additional explicit localities."},"providers":{"type":"array","minItems":1,"items":{"type":"string","enum":["google","bing","duckduckgo","brave","startpage","qwant"]},"description":"Search providers included in the plan."},"confidence_threshold":{"type":"number","minimum":0,"maximum":1,"description":"Acceptance threshold applied to retained classification evidence."},"follow_aggregators":{"type":"boolean","description":"Discover outgoing URLs from saved aggregator pages."},"status":{"type":"string","enum":["active","paused","completed"]},"plan_version":{"type":"integer","minimum":1}},"required":["location","nearby_locations","providers","confidence_threshold","follow_aggregators","status","plan_version"]}}}}}}}}}}},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"before","in":"query","required":false,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}}],"x-curl-examples":[{"title":"Read immutable mass-crawl settings history","code":"curl -X GET \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/00000000-0000-4000-8000-000000000001/settings-history\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/hallpass/runs":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"List staff runs","description":"The 50 newest runs by any staff member, as last read. Read a run to refresh it.","responses":{"200":{"description":"The newest runs.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["runs"],"properties":{"runs":{"type":"array","items":{"$ref":"#/components/schemas/HallPassRun"}}}}}},"example":{"success":true,"data":{"runs":[{"id":"0c7e5a2b-9d14-4f6a-8b3e-2a1d5c9f7e40","kind":"general","summary":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","rehearsal":false,"maxSpend":{"amountCents":6000,"currency":"USD"},"autoAcceptDecisions":false,"requesterUserId":"4b2e7c1a-9d3f-4e8b-a6c5-1f0d2e3c4b5a","request":{"instruction":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","startUrl":"https://greenbowl.example/order","sessionId":null,"merchant":null},"state":"awaiting_decision","refusal":null,"mode":"automatic","stage":null,"hold":null,"decision":{"decisionId":"2a6c9e1b-4d8f-4b3a-9c7e-5f1d0a8b3c26","code":"confirm_total","message":"Pay USD 48.20 to Green Bowl Kitchen for two dinner bowls?","options":[{"optionId":"confirm","label":"Pay USD 48.20","total":{"amountCents":4820,"currency":"USD"}}],"proposedOptionId":null,"expiresAt":"2026-10-09T19:00:00Z"},"decisions":[],"receipt":null,"failure":null,"uncertainty":null,"pollAfterMs":15000,"deadlineAt":"2026-10-09T19:00:00Z","createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T17:02:00Z"}]}}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","x-curl-examples":[{"title":"List staff runs","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/hallpass/runs\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Start a staff run","description":"Starts a Hallpass task and returns it as Hallpass first reports it. A run with no `kind` is general; a run places the order by default, spending up to `maxSpend` or the staff cap (general runs still wait for staff to confirm the exact total); `rehearsal` stops at the checkout total and orders nothing. A camp booking run reads its target from the catalog session.","responses":{"200":{"description":"The existing run for this idempotency key, returned because the request was a resend.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["run"],"properties":{"run":{"$ref":"#/components/schemas/HallPassRun"}}}}},"example":{"success":true,"data":{"run":{"id":"0c7e5a2b-9d14-4f6a-8b3e-2a1d5c9f7e40","kind":"general","summary":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","rehearsal":false,"maxSpend":{"amountCents":6000,"currency":"USD"},"autoAcceptDecisions":false,"requesterUserId":"4b2e7c1a-9d3f-4e8b-a6c5-1f0d2e3c4b5a","request":{"instruction":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","startUrl":"https://greenbowl.example/order","sessionId":null,"merchant":null},"state":"awaiting_decision","refusal":null,"mode":"automatic","stage":null,"hold":null,"decision":{"decisionId":"2a6c9e1b-4d8f-4b3a-9c7e-5f1d0a8b3c26","code":"confirm_total","message":"Pay USD 48.20 to Green Bowl Kitchen for two dinner bowls?","options":[{"optionId":"confirm","label":"Pay USD 48.20","total":{"amountCents":4820,"currency":"USD"}}],"proposedOptionId":null,"expiresAt":"2026-10-09T19:00:00Z"},"decisions":[],"receipt":null,"failure":null,"uncertainty":null,"pollAfterMs":15000,"deadlineAt":"2026-10-09T19:00:00Z","createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T17:02:00Z"}}}}}},"201":{"description":"The run was created.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["run"],"properties":{"run":{"$ref":"#/components/schemas/HallPassRun"}}}}},"example":{"success":true,"data":{"run":{"id":"0c7e5a2b-9d14-4f6a-8b3e-2a1d5c9f7e40","kind":"general","summary":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","rehearsal":false,"maxSpend":{"amountCents":6000,"currency":"USD"},"autoAcceptDecisions":false,"requesterUserId":"4b2e7c1a-9d3f-4e8b-a6c5-1f0d2e3c4b5a","request":{"instruction":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","startUrl":"https://greenbowl.example/order","sessionId":null,"merchant":null},"state":"awaiting_decision","refusal":null,"mode":"automatic","stage":null,"hold":null,"decision":{"decisionId":"2a6c9e1b-4d8f-4b3a-9c7e-5f1d0a8b3c26","code":"confirm_total","message":"Pay USD 48.20 to Green Bowl Kitchen for two dinner bowls?","options":[{"optionId":"confirm","label":"Pay USD 48.20","total":{"amountCents":4820,"currency":"USD"}}],"proposedOptionId":null,"expiresAt":"2026-10-09T19:00:00Z"},"decisions":[],"receipt":null,"failure":null,"uncertainty":null,"pollAfterMs":15000,"deadlineAt":"2026-10-09T19:00:00Z","createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T17:02:00Z"}}}}}},"400":{"description":"The details are invalid, or `maxSpend` is above the deployment’s cap.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Full orders are switched off in this deployment: the staff spending cap is 0. Start a rehearsal, or raise HALLPASS_STAFF_MAX_SPEND_CENTS.","data":null}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}},"404":{"description":"The camp booking’s session is not in the catalog.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"That session is not in the catalog.","data":null}}}},"409":{"description":"This idempotency key was already used for different details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"This idempotency key was already used for a different run.","data":null}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","x-curl-examples":[{"title":"Start a staff run","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/hallpass/runs\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"idempotencyKey\": \"5d3c1b2a-7e6f-4a8b-9c0d-1e2f3a4b5c6d\", \"maxSpend\": {\"amountCents\": 6000, \"currency\": \"USD\"}, \"general\": {\"instruction\": \"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.\", \"startUrl\": \"https://greenbowl.example/order\", \"subjects\": [{\"role\": \"recipient\", \"fields\": {\"full_name\": \"Sam Rivera\", \"street_address\": \"18 Alder Lane\", \"postal_code\": \"97214\"}}]}}'"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunInput"},"example":{"idempotencyKey":"5d3c1b2a-7e6f-4a8b-9c0d-1e2f3a4b5c6d","maxSpend":{"amountCents":6000,"currency":"USD"},"general":{"instruction":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","startUrl":"https://greenbowl.example/order","subjects":[{"role":"recipient","fields":{"full_name":"Sam Rivera","street_address":"18 Alder Lane","postal_code":"97214"}}]}}}}}}},"/api/hallpass/runs/{runId}":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Get a staff run","description":"Returns the run after resending a task Hallpass has not acknowledged, or reading Hallpass’s status while it can still change. A Hallpass outage returns the run as last read.","responses":{"200":{"description":"The run.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["run"],"properties":{"run":{"$ref":"#/components/schemas/HallPassRun"}}}}},"example":{"success":true,"data":{"run":{"id":"0c7e5a2b-9d14-4f6a-8b3e-2a1d5c9f7e40","kind":"general","summary":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","rehearsal":false,"maxSpend":{"amountCents":6000,"currency":"USD"},"autoAcceptDecisions":false,"requesterUserId":"4b2e7c1a-9d3f-4e8b-a6c5-1f0d2e3c4b5a","request":{"instruction":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","startUrl":"https://greenbowl.example/order","sessionId":null,"merchant":null},"state":"awaiting_decision","refusal":null,"mode":"automatic","stage":null,"hold":null,"decision":{"decisionId":"2a6c9e1b-4d8f-4b3a-9c7e-5f1d0a8b3c26","code":"confirm_total","message":"Pay USD 48.20 to Green Bowl Kitchen for two dinner bowls?","options":[{"optionId":"confirm","label":"Pay USD 48.20","total":{"amountCents":4820,"currency":"USD"}}],"proposedOptionId":null,"expiresAt":"2026-10-09T19:00:00Z"},"decisions":[],"receipt":null,"failure":null,"uncertainty":null,"pollAfterMs":15000,"deadlineAt":"2026-10-09T19:00:00Z","createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T17:02:00Z"}}}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}},"404":{"description":"No run has this ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Run not found.","data":null}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","x-curl-examples":[{"title":"Get a staff run","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/hallpass/runs/$RUN_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/hallpass/runs/{runId}/decisions/{decisionId}":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Answer a staff run’s decision","description":"Accepting resumes the task; accepting a `confirm_total` decision lets Hallpass pay exactly that total. Declining ends the run. Only a signed-in staff or ADMIN account answers: a named machine key gets 403, because accepting can approve a payment.","responses":{"200":{"description":"The run after the answer.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["run"],"properties":{"run":{"$ref":"#/components/schemas/HallPassRun"}}}}},"example":{"success":true,"data":{"run":{"id":"0c7e5a2b-9d14-4f6a-8b3e-2a1d5c9f7e40","kind":"general","summary":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","rehearsal":false,"maxSpend":{"amountCents":6000,"currency":"USD"},"autoAcceptDecisions":false,"requesterUserId":"4b2e7c1a-9d3f-4e8b-a6c5-1f0d2e3c4b5a","request":{"instruction":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","startUrl":"https://greenbowl.example/order","sessionId":null,"merchant":null},"state":"awaiting_decision","refusal":null,"mode":"automatic","stage":null,"hold":null,"decision":{"decisionId":"2a6c9e1b-4d8f-4b3a-9c7e-5f1d0a8b3c26","code":"confirm_total","message":"Pay USD 48.20 to Green Bowl Kitchen for two dinner bowls?","options":[{"optionId":"confirm","label":"Pay USD 48.20","total":{"amountCents":4820,"currency":"USD"}}],"proposedOptionId":null,"expiresAt":"2026-10-09T19:00:00Z"},"decisions":[],"receipt":null,"failure":null,"uncertainty":null,"pollAfterMs":15000,"deadlineAt":"2026-10-09T19:00:00Z","createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T17:02:00Z"}}}}}},"400":{"description":"The answer is invalid or names an option the decision didn’t offer, or Hallpass refused a typed answer that repeats an attached detail or holds a token, card number or email address (the message says which; fix the answer and send it again).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Choose one of the offered options.","data":null}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}},"404":{"description":"No run has this ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Run not found.","data":null}}}},"409":{"description":"The decision is no longer waiting, or already has an immutable sealed answer. Refresh checks status only; an answer cannot be replaced or replayed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"That decision is no longer waiting for an answer.","data":null}}}},"422":{"description":"This secret field cannot be collected or stored here, including security codes, card numbers and verification codes. The answer was not sealed or persisted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"503":{"description":"Hallpass did not take the answer, or this deployment cannot seal a secret answer (no HALLPASS_SECRETS_KEY).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"The answer could not be sent. Try again.","data":null}}}}},"security":[{"AccountCredential":[]}],"x-auth":"Current signed-in staff or ADMIN account acting as itself: the Intelligence session cookie or that account’s credential. Machine keys, previews, bare service credentials and delegated service requests are rejected, so every run names the person who started it. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when the API has HALLPASS_URL and HALLPASS_SERVICE_TOKEN; otherwise 404.","x-curl-examples":[{"title":"Confirm the total","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/hallpass/runs/$RUN_ID/decisions/$DECISION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"outcome\": \"accept\", \"optionId\": \"confirm\"}'"}],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"decisionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["outcome"],"properties":{"outcome":{"type":"string","enum":["accept","decline"]},"optionId":{"type":["string","null"],"description":"Required to accept."},"answer":{"type":["string","null"],"minLength":1,"maxLength":2000,"description":"Your typed answer. Required to accept an `answer_required` decision; omit it otherwise."}}},"example":{"outcome":"accept","optionId":"confirm"}}}}}},"/api/hallpass/runs/{runId}/decisions/{decisionId}/notices":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Record that the run page showed a decision","description":"The run page reports what it did with a waiting decision for the run's requester: `seen` when the decision was on screen, `notified` when the page raised a browser notification for it while hidden. Only the requester's own signed-in page counts, and only for the decision still waiting; any other caller, a stale decision or a repeat records nothing and answers `recorded: false`. Each record ships `decision_seen` or `decision_delivered` (channel `web`) once.","responses":{"200":{"description":"Whether this call recorded anything.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["recorded"],"properties":{"recorded":{"type":"boolean"}}}}},"example":{"success":true,"data":{"recorded":true}}}}},"400":{"description":"The event is not `seen` or `notified`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"event must be seen or notified.","data":null}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}},"404":{"description":"No run has this ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Run not found.","data":null}}}}},"security":[{"AccountCredential":[]}],"x-auth":"Current signed-in staff or ADMIN account acting as itself: the Intelligence session cookie or that account’s credential. Machine keys, previews, bare service credentials and delegated service requests are rejected, so every run names the person who started it. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when the API has HALLPASS_URL and HALLPASS_SERVICE_TOKEN; otherwise 404.","x-curl-examples":[{"title":"Record that the requester saw a decision","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/hallpass/runs/$RUN_ID/decisions/$DECISION_ID/notices\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"event\": \"seen\"}'"}],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"decisionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["event"],"properties":{"event":{"type":"string","enum":["seen","notified"]}}},"example":{"event":"seen"}}}}}},"/api/hallpass/runs/{runId}/cancel":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Cancel a staff run","description":"Cancels the run until Hallpass has started paying.","responses":{"200":{"description":"The run after the cancellation.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["run"],"properties":{"run":{"$ref":"#/components/schemas/HallPassRun"}}}}},"example":{"success":true,"data":{"run":{"id":"0c7e5a2b-9d14-4f6a-8b3e-2a1d5c9f7e40","kind":"general","summary":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","rehearsal":false,"maxSpend":{"amountCents":6000,"currency":"USD"},"autoAcceptDecisions":false,"requesterUserId":"4b2e7c1a-9d3f-4e8b-a6c5-1f0d2e3c4b5a","request":{"instruction":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","startUrl":"https://greenbowl.example/order","sessionId":null,"merchant":null},"state":"awaiting_decision","refusal":null,"mode":"automatic","stage":null,"hold":null,"decision":{"decisionId":"2a6c9e1b-4d8f-4b3a-9c7e-5f1d0a8b3c26","code":"confirm_total","message":"Pay USD 48.20 to Green Bowl Kitchen for two dinner bowls?","options":[{"optionId":"confirm","label":"Pay USD 48.20","total":{"amountCents":4820,"currency":"USD"}}],"proposedOptionId":null,"expiresAt":"2026-10-09T19:00:00Z"},"decisions":[],"receipt":null,"failure":null,"uncertainty":null,"pollAfterMs":15000,"deadlineAt":"2026-10-09T19:00:00Z","createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T17:02:00Z"}}}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}},"404":{"description":"No run has this ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Run not found.","data":null}}}},"409":{"description":"Hallpass has not acknowledged the run yet, or it is already finished.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"This run is already finished.","data":null}}}},"503":{"description":"Hallpass did not take the cancellation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"The cancellation could not be sent. Try again.","data":null}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","x-curl-examples":[{"title":"Cancel a staff run","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/hallpass/runs/$RUN_ID/cancel\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/hallpass/runs/{runId}/reconcile":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Reconcile an uncertain staff run","description":"Records how a run that ended `uncertain` really ended, as the signed-in staff member attests: `confirmed` with the order reference, merchant and charged amount, or `failed` with a provider failure code and why (nothing was charged), with a statement and the evidence relied on (the merchant’s order history, the platform-card ledger, a confirmation page or email). A confirmed reconciliation becomes the run’s receipt and must satisfy its approval: a confirmation item among the evidence, at most the spending limit, and for a general run exactly a total the requester confirmed. While Pilgrim may still be acting on the run (its task running or queued and its final account not yet on the run), it is 409. Hallpass records it once: the same `reconciliationId` and content answers with the run as reconciled, and anything else on a reconciled or not uncertain run is 409. Only a signed-in staff or ADMIN account reconciles; a named machine key gets 403. For an explicit free_registration run, send registration (providerReference and merchant) instead of charge; no card or payment is recorded.","security":[{"AccountCredential":[]}],"x-auth":"Current signed-in staff or ADMIN account acting as itself: the Intelligence session cookie or that account’s credential. Machine keys, previews, bare service credentials and delegated service requests are rejected, so every run names the person who started it. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when the API has HALLPASS_URL and HALLPASS_SERVICE_TOKEN; otherwise 404.","x-curl-examples":[{"title":"Reconcile as confirmed","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/hallpass/runs/$RUN_ID/reconcile\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"reconciliationId\": \"9e4b2c7a-1f3d-4a8e-b6c5-0d2e7f1a3b48\", \"outcome\": \"confirmed\", \"charge\": {\"providerReference\": \"GB-20417\", \"merchant\": \"Green Bowl Kitchen\", \"amount\": {\"amountCents\": 4820, \"currency\": \"USD\"}}, \"statement\": \"The merchant order history shows GB-20417 for USD 48.20.\", \"evidence\": [{\"kind\": \"confirmation_page\", \"reference\": \"pilgrim:7d3f0b2e-5a1c-4e9b-8f6d-2c4a9e1b7f30\", \"summary\": \"Pilgrim read the order confirmation for GB-20417, USD 48.20.\"}]}'"}],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["reconciliationId","outcome","statement","evidence"],"properties":{"reconciliationId":{"type":"string","format":"uuid","description":"Chosen by the client once per attempt and sent again on a retry, so a retry is the same reconciliation."},"outcome":{"type":"string","enum":["confirmed","failed"]},"charge":{"description":"Required to reconcile a paid run as confirmed; absent for an explicit free registration.","oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["providerReference","merchant","amount"],"properties":{"providerReference":{"type":"string","minLength":1,"maxLength":120,"description":"The merchant’s order or booking reference."},"merchant":{"type":"string","minLength":1,"maxLength":200},"amount":{"$ref":"#/components/schemas/HallPassRunMoney","description":"What the platform card was charged; positive."}}}]},"failure":{"description":"Required, and only allowed, to reconcile as `failed`: the provider’s reason the order did not go through.","oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"string","enum":["payment_declined","not_available","unsupported_provider","provider_unavailable","blocked_by_provider","needs_person","sold_out","registration_closed","eligibility_mismatch"],"description":"A provider outcome; `sold_out`, `registration_closed` and `eligibility_mismatch` are for camp bookings only."},"message":{"type":"string","minLength":1,"maxLength":500}}}]},"statement":{"type":"string","minLength":1,"maxLength":2000,"description":"What the staff member checked and found, in their words."},"evidence":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"object","additionalProperties":false,"required":["kind","reference","summary"],"properties":{"kind":{"type":"string","enum":["confirmation_page","confirmation_email","provider_account_listing","payment_ledger"]},"reference":{"type":"string","minLength":1,"maxLength":500,"description":"Where the evidence is, such as `pilgrim:<task id>`, the merchant’s order number or the platform-card ledger entry."},"summary":{"type":"string","minLength":1,"maxLength":1000,"description":"What it shows, in words."}}}},"registration":{"description":"Required instead of charge only for a run with the explicit free_registration boundary. Attests a completed registration with no payment. Confirmation evidence is required.","oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["providerReference","merchant"],"properties":{"providerReference":{"type":"string","minLength":1,"maxLength":120,"description":"The merchant’s order or booking reference."},"merchant":{"type":"string","minLength":1,"maxLength":200}}}]}}},"example":{"reconciliationId":"9e4b2c7a-1f3d-4a8e-b6c5-0d2e7f1a3b48","outcome":"confirmed","charge":{"providerReference":"GB-20417","merchant":"Green Bowl Kitchen","amount":{"amountCents":4820,"currency":"USD"}},"statement":"The merchant’s order history shows GB-20417 placed at 17:05 for USD 48.20, and the platform-card ledger shows the charge.","evidence":[{"kind":"confirmation_page","reference":"pilgrim:7d3f0b2e-5a1c-4e9b-8f6d-2c4a9e1b7f30","summary":"Pilgrim read the order confirmation for GB-20417, USD 48.20."},{"kind":"payment_ledger","reference":"Platform card ledger, 2026-10-09 17:05","summary":"One charge of USD 48.20 from Green Bowl Kitchen."}]}}}},"responses":{"200":{"description":"The run as reconciled, or as already reconciled by this same request.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["run"],"properties":{"run":{"$ref":"#/components/schemas/HallPassRun"}}}}},"example":{"success":true,"data":{"run":{"id":"0c7e5a2b-9d14-4f6a-8b3e-2a1d5c9f7e40","kind":"general","summary":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","rehearsal":false,"maxSpend":{"amountCents":6000,"currency":"USD"},"autoAcceptDecisions":false,"requesterUserId":"4b2e7c1a-9d3f-4e8b-a6c5-1f0d2e3c4b5a","request":{"instruction":"Order dinner for two from Green Bowl Kitchen, delivered by 6:30 pm.","startUrl":"https://greenbowl.example/order","sessionId":null,"merchant":null},"state":"confirmed","refusal":null,"mode":"automatic","stage":null,"hold":null,"decision":null,"decisions":[{"decisionId":"2a6c9e1b-4d8f-4b3a-9c7e-5f1d0a8b3c26","code":"confirm_total","label":"Confirm the total","outcome":"accepted","optionId":"confirm","total":{"amountCents":4820,"currency":"USD"},"decidedBy":"requester","decidedAt":"2026-10-09T17:03:00Z"}],"receipt":{"providerReference":"GB-20417","providerStatus":"confirmed","merchant":"Green Bowl Kitchen","paymentStatus":"charged","amount":{"amountCents":4820,"currency":"USD"},"amountDue":null,"lineItems":[{"kind":"item","label":"Green Bowl Kitchen","amount":{"amountCents":4820,"currency":"USD"}}],"evidence":[{"kind":"confirmation_page","reference":"pilgrim:7d3f0b2e-5a1c-4e9b-8f6d-2c4a9e1b7f30","summary":"Pilgrim read the order confirmation for GB-20417, USD 48.20.","capturedAt":"2026-10-09T18:10:00Z"}],"confirmedAt":"2026-10-09T18:10:00Z","bookedSession":null,"fulfillment":{"summary":"Order GB-20417 with Green Bowl Kitchen, reconciled by School's Out staff.","startsAt":null,"endsAt":null}},"failure":null,"uncertainty":null,"pollAfterMs":null,"deadlineAt":"2026-10-09T19:00:00Z","createdAt":"2026-10-09T17:00:00Z","updatedAt":"2026-10-09T18:10:00Z","reconciliation":{"reconciliationId":"9e4b2c7a-1f3d-4a8e-b6c5-0d2e7f1a3b48","reconciledBy":"4b2e7c1a-9d3f-4e8b-a6c5-1f0d2e3c4b5a","statement":"The merchant’s order history shows GB-20417 placed at 17:05 for USD 48.20, and the platform-card ledger shows the charge.","evidence":[{"kind":"confirmation_page","reference":"pilgrim:7d3f0b2e-5a1c-4e9b-8f6d-2c4a9e1b7f30","summary":"Pilgrim read the order confirmation for GB-20417, USD 48.20."},{"kind":"payment_ledger","reference":"Platform card ledger, 2026-10-09 17:05","summary":"One charge of USD 48.20 from Green Bowl Kitchen."}],"cardAttested":false,"reconciledAt":"2026-10-09T18:10:00Z"}}}}}}},"400":{"description":"The body is not a valid reconciliation, or a confirmed one does not satisfy the run’s approval; the message names the field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"The amount is not a total the requester confirmed (charge.amount).","data":null}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}},"404":{"description":"No run has this ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Run not found.","data":null}}}},"409":{"description":"The run is not `uncertain` (it is still running, already ended otherwise, or already reconciled), Pilgrim has not finished with it yet, or this `reconciliationId` was recorded with different content.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"This run is not waiting for reconciliation.","data":null}}}},"503":{"description":"Hallpass did not take the reconciliation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"The reconciliation could not be sent. Try again.","data":null}}}}}}},"/api/hallpass/plans":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Plan a staff run","description":"Reads a request the way Hallpass would, without starting anything: send `sessionId` to plan a camp booking, or a `startUrl` or instruction to find catalog sessions on that site. Use the answer to choose the run's kind.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"instruction":{"type":"string"},"startUrl":{"type":"string"},"sessionId":{"type":"string","format":"uuid"}}},"example":{"startUrl":"https://lakeside.example/register"}}}},"responses":{"200":{"description":"The plan.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["plan"],"properties":{"plan":{"$ref":"#/components/schemas/HallPassPlan"}}}}},"example":{"success":true,"data":{"plan":{"kind":"general","reason":"This page belongs to Lakeside Science Camp, which School’s Out lists. Choose one of its sessions to book it as a camp booking, or keep it as a general task.","sessions":[{"sessionId":"c3a9e1f2-6d4b-4a8c-b2e7-0f5d9c1a3b48","sessionName":"Robotics Week","organizationName":"Lakeside Science Camp","startDate":"2027-07-12","endDate":"2027-07-16"}],"missing":[]}}}}}},"400":{"description":"The body has an unknown property or an invalid session ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"sessionId must be a UUID.","data":null}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}},"404":{"description":"The session is not in the catalog.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"That session is not in the catalog.","data":null}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","x-curl-examples":[{"title":"Plan a staff run","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/hallpass/plans\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"startUrl\": \"https://lakeside.example/register\"}'"}]}},"/api/users/viewer/children/{childId}/photo":{"get":{"tags":["Internal family profiles"],"x-internal":true,"summary":"Read a child's photo","description":"The JPEG, to the child's owner and guardians only. With `v` naming the current photo it may be cached privately until it changes; never in a shared cache.","parameters":[{"name":"childId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}},{"name":"v","in":"query","required":false,"schema":{"type":"string"},"description":"The photo's hash, from photoUrl."}],"responses":{"200":{"description":"The photo.","content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Current account required."},"404":{"description":"Child not found for this account, or no photo."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"title":"Read a child's photo","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/photo\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"security":[{"AccountCredential":[]}]},"put":{"tags":["Internal family profiles"],"x-internal":true,"summary":"Upload a child's photo","description":"Body is the image (image/jpeg or image/png, at most 2 MiB and 4096 px a side), through the parent photo's pipeline: cropped to a square and stored as a 512×512 JPEG without metadata. Owners and guardians may set it. Only the child's family reads it.","requestBody":{"required":true,"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["photoUrl"],"properties":{"photoUrl":{"type":"string"}}}}}}}},"401":{"description":"Current account required."},"413":{"description":"Larger than 2 MiB."},"415":{"description":"Not image/jpeg or image/png."},"422":{"description":"Cannot be decoded, or too large in pixels."},"404":{"description":"Child not found for this account."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not add or edit children (edit_children; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"title":"Upload a child's photo","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/photo\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: image/jpeg\" --data-binary @photo.jpg"}],"parameters":[{"name":"childId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}]},"delete":{"tags":["Internal family profiles"],"x-internal":true,"summary":"Remove a child's photo","description":"Removes the child's photo, if it has one.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["photoUrl"],"properties":{"photoUrl":{"type":"null"}}}}}}}},"401":{"description":"Current account required."},"404":{"description":"Child not found for this account."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"403":{"description":"The role may not add or edit children (edit_children; a caregiver)"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Child profiles are visible to their owner and accepted active guardians; unrelated accounts receive 404.","x-curl-examples":[{"title":"Remove a child's photo","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/photo\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"childId","in":"path","required":true,"schema":{"format":"uuid","type":"string"}}]}},"/api/users/viewer/email-change":{"post":{"tags":["Internal family account"],"x-internal":true,"summary":"Change your email","description":"Emails a link to the new address; the account keeps its email (profile.pendingEmail shows the new one) until that link is confirmed with POST /api/users/viewer/email-change/confirm. Asking again replaces the pending change and its link. The link opens redirect?code=<code> when redirect is an absolute URL on an allow-listed origin (AUTH_ORIGIN, PARENT_SITE_ORIGIN, API_ASSISTANT_ORIGINS), else the Auth app's /email-change?code=<code>. It expires in 30 minutes. Outside production, when no email is sent, data.devLink is the link.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["email"],"properties":{"email":{"format":"email","maxLength":254,"type":"string"},"redirect":{"description":"The client's page for the link, e.g. https://next.schoolsoutapp.com/consumer/email-change.","format":"uri","type":"string"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["pending"],"properties":{"pending":{"type":"object","additionalProperties":false,"required":["email","expiresAt"],"properties":{"email":{"format":"email","type":"string"},"expiresAt":{"format":"date-time","type":"string"}}},"devLink":{"description":"Development only.","type":"string"}}}}}}}},"400":{"description":"Not an email address, or already the account's email."},"401":{"description":"A signed-in device is required."},"409":{"description":"Another account holds this address."},"429":{"description":"Too many email changes; retry later."}},"security":[{"AccountCredential":[]}],"x-auth":"A signed-in device only: the account credential with its live session (cookie sid or X-Session-Id); a bare account key, machine keys and delegated service requests are rejected.","x-curl-examples":[{"title":"Change your email","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/email-change\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" -d '{\"email\":\"new@example.com\"}'"}]}},"/api/users/viewer/email-change/confirm":{"post":{"tags":["Internal family account"],"x-internal":true,"summary":"Confirm your new email","description":"Confirms the pending change with the code from its link, on the account that asked. In one transaction the account moves to the new address (verified), its key rotates, every session is signed out (revoked_reason email_change) and this device gets a new session. The answer is a sign-in's: {user, key, session_id}, so the client re-signs its cookie from it. The old address is told of the change.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"format":"uuid","type":"string"}}}}}},"responses":{"200":{"description":"The new credential.","content":{"application/json":{"schema":{"type":"object","required":["user","key","session_id"],"properties":{"user":{"type":"object","description":"The users row without its key and password hash; email is the new address."},"key":{"type":"string"},"session_id":{"format":"uuid","type":"string"}}}}}},"401":{"description":"A signed-in device is required, or this device's key was rotated meanwhile."},"409":{"description":"Another account took this address meanwhile."},"410":{"description":"The link expired, was replaced by a newer change, or is another account's."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"A signed-in device only: the account credential with its live session (cookie sid or X-Session-Id); a bare account key, machine keys and delegated service requests are rejected.","x-curl-examples":[{"title":"Confirm your new email","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/email-change/confirm\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" -d '{\"code\":\"CODE\"}'"}]}},"/api/users/viewer/phone":{"delete":{"tags":["Internal family account"],"x-internal":true,"summary":"Remove your phone","description":"Clears the verified phone and the contact phone. SMS two-step login turns off with it, so while it is on this needs the same proof as turning it off. Text notifications stop: they go only to a verified phone. To replace the phone, verify the new one (POST /api/auth/phone/check), which answers the same proof while SMS two-step login is on.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"currentPassword":{"description":"The account's password, when it has one (profile.hasPassword).","type":"string"},"currentPhoneCode":{"description":"A code texted to the phone two-step login uses, by POST /api/users/viewer/two-step/sms/code.","type":"string"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["profile"],"properties":{"profile":{"$ref":"#/components/schemas/FamilyAccountViewerProfile"}}}}}}}},"400":{"description":"Proof is missing (code proof-required), or the password or texted code is wrong or expired (code proof-wrong). Asked only while SMS two-step login is on."},"401":{"description":"A signed-in device is required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"A signed-in device only: the account credential with its live session (cookie sid or X-Session-Id); a bare account key, machine keys and delegated service requests are rejected.","x-curl-examples":[{"title":"Remove your phone","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/phone\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" -d '{}'"}]}},"/api/users/viewer/two-step/sms":{"post":{"tags":["Internal family account"],"x-internal":true,"summary":"Turn on SMS two-step login","description":"Signing in then asks for a code texted to the account's verified phone. Already on is a success.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["profile"],"properties":{"profile":{"$ref":"#/components/schemas/FamilyAccountViewerProfile"}}}}}}}},"401":{"description":"A signed-in device is required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"409":{"description":"The account has no verified phone (code phone-unverified). Verify one with POST /api/auth/phone/start and /api/auth/phone/check first."}},"security":[{"AccountCredential":[]}],"x-auth":"A signed-in device only: the account credential with its live session (cookie sid or X-Session-Id); a bare account key, machine keys and delegated service requests are rejected.","x-curl-examples":[{"title":"Turn on SMS two-step login","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/two-step/sms\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" -d '{}'"}]},"delete":{"tags":["Internal family account"],"x-internal":true,"summary":"Turn off SMS two-step login","description":"Needs fresh proof that it is the parent: currentPassword or currentPhoneCode. The verified phone stays, and keeps getting the texts notifications send. Already off is a success and needs no proof.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"currentPassword":{"description":"The account's password, when it has one (profile.hasPassword).","type":"string"},"currentPhoneCode":{"description":"A code texted to the phone two-step login uses, by POST /api/users/viewer/two-step/sms/code.","type":"string"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["profile"],"properties":{"profile":{"$ref":"#/components/schemas/FamilyAccountViewerProfile"}}}}}}}},"400":{"description":"Proof is missing (code proof-required), or the password or texted code is wrong or expired (code proof-wrong)."},"401":{"description":"A signed-in device is required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"A signed-in device only: the account credential with its live session (cookie sid or X-Session-Id); a bare account key, machine keys and delegated service requests are rejected.","x-curl-examples":[{"title":"Turn off SMS two-step login","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/two-step/sms\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" -d '{\"currentPassword\":\"...\"}'"}]}},"/api/users/viewer/two-step/sms/code":{"post":{"tags":["Internal family account"],"x-internal":true,"summary":"Text a code to confirm it’s you","description":"Texts a code to the phone SMS two-step login uses. Send it back as currentPhoneCode to turn SMS two-step login off, remove the phone, or verify a different phone. Outside production, when no text is sent, data.devCode is the code.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["sent"],"properties":{"sent":{"const":true},"devCode":{"description":"Development only.","type":"string"}}}}}}}},"400":{"description":"SMS two-step login is off, so no change needs a texted code."},"401":{"description":"A signed-in device is required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"502":{"description":"The text could not be sent."}},"security":[{"AccountCredential":[]}],"x-auth":"A signed-in device only: the account credential with its live session (cookie sid or X-Session-Id); a bare account key, machine keys and delegated service requests are rejected.","x-curl-examples":[{"title":"Text a code to confirm it’s you","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/two-step/sms/code\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"X-Session-Id: $SESSION_ID\""}]}},"/api/users/reset-password-preview":{"post":{"tags":["Internal sign-in handoff"],"summary":"Preview a password reset code","description":"Says whose account a reset link's code is for, masked (first character and domain, e.g. h•••@internet.dev), without spending the code, so the reset screen can name the account. Shares a per-client limit with reset-password-confirm.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"The code from the reset link."}}}}}},"responses":{"200":{"description":"The account's masked email.","content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string"}}}}}},"401":{"description":"Unknown, expired or malformed code."},"429":{"description":"Too many reset codes from this client; retry later."}},"security":[],"x-auth":"Public; the reset code is the credential.","x-curl-examples":[{"title":"Preview a password reset code","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/reset-password-preview\" \\\n  -H \"Content-Type: application/json\" -d '{\"code\":\"RESET_CODE\"}'"}],"x-internal":true}},"/api/users/viewer/family/members":{"get":{"summary":"List the family's members and invitations","description":"The family the viewer acts in: its owner, members (any adult of the family may read it) and, for its parents, its open and recently declined invitations. A caregiver sees names only. An adult in no other family is the owner of their own, possibly with no members.","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"$ref":"#/components/schemas/FamilyMembers"}}}}}},"401":{"description":"Current account required"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The viewer acts in one family: the family they are a member of, else their own (FamilyAccess); each route requires an action of the role permission matrix and answers 403 with that action's refusal otherwise.","x-curl-examples":[{"title":"List the family's members and invitations","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/users/viewer/family/members\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"security":[{"AccountCredential":[]}]}},"/api/users/viewer/family/invitations":{"post":{"summary":"Invite an adult into the family","description":"Owner only (manage_members). Creates an invitation valid for seven days with a role and children, and queues its email in the outbox in the same transaction. The response reveals inviteUrl once. The token is 32 random bytes (43 URL-safe base64 characters) in the fragment; only its SHA-256 is stored. Accepting needs the invited address. An expired open invitation to the address is replaced. Issuing links (create, resend, copy link, and the per-child invite) is limited to 20 an hour per owner.","tags":["Internal family account"],"x-internal":true,"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["invitation","inviteUrl","emailSent"],"properties":{"invitation":{"$ref":"#/components/schemas/FamilyInvitation"},"inviteUrl":{"type":"string","format":"uri","description":"Revealed once; the token lives in the URL fragment."},"emailSent":{"type":"boolean","description":"The invitation email was queued in the outbox (false when the deployment has no API_AES_KEY to seal it; copy the link instead)."}}}}}}}},"400":{"description":"Invalid invitation, own address"},"401":{"description":"Current account required"},"403":{"description":"Not the family's owner"},"404":{"description":"A child that is not the family's"},"409":{"description":"An open invitation for the address, an adult already in the family, or a child at the five-adult limit"},"429":{"description":"Invitation rate limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The viewer acts in one family: the family they are a member of, else their own (FamilyAccess); each route requires an action of the role permission matrix and answers 403 with that action's refusal otherwise.","x-curl-examples":[{"title":"Invite an adult into the family","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/family/invitations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"ruth@example.com\", \"name\": \"Ruth Bello\", \"role\": \"caregiver\", \"relation\": \"Grandparent\", \"childIds\": [\"1f8a3c5e-7b2d-4e9f-a6c1-4d0b8e2f7a93\"]}'"}],"security":[{"AccountCredential":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","role"],"properties":{"email":{"type":"string","format":"email","maxLength":254},"name":{"type":["string","null"],"maxLength":120},"role":{"$ref":"#/components/schemas/FamilyMemberRole"},"relation":{"type":["string","null"],"maxLength":60},"childIds":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":20,"uniqueItems":true},"allChildren":{"const":true,"type":"boolean"},"redirect":{"type":"string","format":"uri","description":"Optional page the link opens, with the token in its fragment (e.g. <site>/consumer/invitation): an absolute URL on AUTH_ORIGIN, PARENT_SITE_ORIGIN or API_ASSISTANT_ORIGINS. Anything else, or none, links to the parent site's accept page, PARENT_SITE_ORIGIN/consumer/invitation."}},"additionalProperties":false,"description":"childIds or allChildren, not both. A co-parent invitation without either sees every child; a caregiver invitation must name them."}}}}}},"/api/users/viewer/family/invitations/{invitationId}/resend":{"post":{"summary":"Resend an invitation","description":"Owner only. Gives an open invitation (pending or expired) a new token, sends it now and expires it seven days from now, and queues the email again. The previous link stops working.","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["invitation","inviteUrl","emailSent"],"properties":{"invitation":{"$ref":"#/components/schemas/FamilyInvitation"},"inviteUrl":{"type":"string","format":"uri","description":"Revealed once; the token lives in the URL fragment."},"emailSent":{"type":"boolean","description":"The invitation email was queued in the outbox (false when the deployment has no API_AES_KEY to seal it; copy the link instead)."}}}}}}}},"401":{"description":"Current account required"},"403":{"description":"Not the family's owner"},"404":{"description":"No such invitation"},"409":{"description":"Accepted, declined or canceled"},"429":{"description":"Invitation rate limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The viewer acts in one family: the family they are a member of, else their own (FamilyAccess); each route requires an action of the role permission matrix and answers 403 with that action's refusal otherwise.","x-curl-examples":[{"title":"Resend an invitation","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/family/invitations/{invitationId}/resend\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"security":[{"AccountCredential":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"redirect":{"type":"string","format":"uri","description":"Optional page the link opens, with the token in its fragment (e.g. <site>/consumer/invitation): an absolute URL on AUTH_ORIGIN, PARENT_SITE_ORIGIN or API_ASSISTANT_ORIGINS. Anything else, or none, links to the parent site's accept page, PARENT_SITE_ORIGIN/consumer/invitation."}},"additionalProperties":false}}}},"parameters":[{"in":"path","name":"invitationId","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/users/viewer/family/invitations/{invitationId}/link":{"post":{"summary":"Copy an invitation link","description":"Owner only. As resend, without an email: a link for the owner to share themselves. The previous link stops working; accepting still needs the invited address.","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["invitation","inviteUrl"],"properties":{"invitation":{"$ref":"#/components/schemas/FamilyInvitation"},"inviteUrl":{"type":"string","format":"uri","description":"Revealed once; the token lives in the URL fragment."}}}}}}}},"401":{"description":"Current account required"},"403":{"description":"Not the family's owner"},"404":{"description":"No such invitation"},"409":{"description":"Accepted, declined or canceled"},"429":{"description":"Invitation rate limit. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The viewer acts in one family: the family they are a member of, else their own (FamilyAccess); each route requires an action of the role permission matrix and answers 403 with that action's refusal otherwise.","x-curl-examples":[{"title":"Copy an invitation link","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/family/invitations/{invitationId}/link\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"security":[{"AccountCredential":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"redirect":{"type":"string","format":"uri","description":"Optional page the link opens, with the token in its fragment (e.g. <site>/consumer/invitation): an absolute URL on AUTH_ORIGIN, PARENT_SITE_ORIGIN or API_ASSISTANT_ORIGINS. Anything else, or none, links to the parent site's accept page, PARENT_SITE_ORIGIN/consumer/invitation."}},"additionalProperties":false}}}},"parameters":[{"in":"path","name":"invitationId","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/users/viewer/family/invitations/{invitationId}":{"delete":{"summary":"Cancel an invitation","description":"Owner only. Cancels an open invitation; its link then previews as canceled.","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["invitation","canceled"],"properties":{"invitation":{"$ref":"#/components/schemas/FamilyInvitation"},"canceled":{"const":true,"type":"boolean"}}}}}}}},"401":{"description":"Current account required"},"403":{"description":"Not the family's owner"},"404":{"description":"No such invitation"},"409":{"description":"Already accepted, declined or canceled"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The viewer acts in one family: the family they are a member of, else their own (FamilyAccess); each route requires an action of the role permission matrix and answers 403 with that action's refusal otherwise.","x-curl-examples":[{"title":"Cancel an invitation","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/family/invitations/{invitationId}\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"security":[{"AccountCredential":[]}],"parameters":[{"in":"path","name":"invitationId","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/users/viewer/family/members/{userId}":{"patch":{"summary":"Change a member's role or children","description":"Owner only. A new role, relation (null clears it), and childIds or allChildren. Children the member no longer sees lose the member's private notes and care requirements about them. The member is told (role_changed).","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["member"],"properties":{"member":{"$ref":"#/components/schemas/FamilyMember"}}}}}}}},"400":{"description":"Nothing to change, or an invalid value"},"401":{"description":"Current account required"},"403":{"description":"Not the family's owner"},"404":{"description":"Not a member of the family, or a child that is not the family's"},"409":{"description":"The owner, or a child at the five-adult limit"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The viewer acts in one family: the family they are a member of, else their own (FamilyAccess); each route requires an action of the role permission matrix and answers 403 with that action's refusal otherwise.","x-curl-examples":[{"title":"Change a member's role or children","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/family/members/{userId}\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"role\": \"co_parent\", \"allChildren\": true}'"}],"security":[{"AccountCredential":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"role":{"$ref":"#/components/schemas/FamilyMemberRole"},"relation":{"type":["string","null"],"maxLength":60},"childIds":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":20,"uniqueItems":true},"allChildren":{"const":true,"type":"boolean"}},"additionalProperties":false,"minProperties":1}}}},"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"}}]},"delete":{"summary":"Remove a member, or leave","description":"The owner removes a member; a member names themselves to leave. The member's grants and private context about the family's children go with the membership. The other side is told (member_removed).","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["removed"],"properties":{"removed":{"const":true,"type":"boolean"}}}}}}}},"401":{"description":"Current account required"},"403":{"description":"A member removing someone else"},"404":{"description":"Not a member of the family"},"409":{"description":"The owner"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The viewer acts in one family: the family they are a member of, else their own (FamilyAccess); each route requires an action of the role permission matrix and answers 403 with that action's refusal otherwise.","x-curl-examples":[{"title":"Remove a member, or leave","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/family/members/{userId}\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"security":[{"AccountCredential":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/users/viewer/family/leave":{"post":{"summary":"Leave the family","description":"A member leaves the family they help with (as DELETE on their own id). The owner is told (member_removed). 409 for the owner.","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["left"],"properties":{"left":{"const":true,"type":"boolean"}}}}}}}},"401":{"description":"Current account required"},"404":{"description":"Not a member"},"409":{"description":"The owner cannot leave their own family"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The viewer acts in one family: the family they are a member of, else their own (FamilyAccess); each route requires an action of the role permission matrix and answers 403 with that action's refusal otherwise.","x-curl-examples":[{"title":"Leave the family","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/family/leave\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"security":[{"AccountCredential":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{},"additionalProperties":false}}}}}},"/api/users/viewer/family-invitations/accept":{"post":{"summary":"Accept a family invitation","description":"The signed-in account whose email is the invited address joins the family with the invitation's role and children, and is told the family as they now see it. Holding the token proves the inbox, so accepting verifies an unverified matching email. One family per adult: a member of another family, an owner with members, or an account with its own child profiles gets 409. An adult already in this family takes the invitation's role and gains its children. The owner is told (member_joined).","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["member","family"],"properties":{"member":{"$ref":"#/components/schemas/FamilyMember"},"family":{"$ref":"#/components/schemas/FamilyMembers"}}}}}}}},"400":{"description":"approve must be true"},"401":{"description":"Current account required"},"403":{"description":"Signed in with another address, or the family's own owner"},"404":{"description":"Unknown, accepted, declined, canceled or expired invitation"},"409":{"description":"One family per adult, or a child at the five-adult limit"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The viewer acts in one family: the family they are a member of, else their own (FamilyAccess); each route requires an action of the role permission matrix and answers 403 with that action's refusal otherwise.","x-curl-examples":[{"title":"Accept a family invitation","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/family-invitations/accept\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"security":[{"AccountCredential":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","approve"],"properties":{"token":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]{43}$"},"approve":{"const":true,"type":"boolean"}},"additionalProperties":false}}}}}},"/api/users/viewer/family-invitations/decline":{"post":{"summary":"Decline a family invitation (signed in)","description":"As the public decline, for a signed-in invitee.","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["status"],"properties":{"status":{"const":"declined","type":"string"}}}}}}}},"401":{"description":"Current account required"},"404":{"description":"Unknown token"},"409":{"description":"No longer pending"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. The viewer acts in one family: the family they are a member of, else their own (FamilyAccess); each route requires an action of the role permission matrix and answers 403 with that action's refusal otherwise.","x-curl-examples":[{"title":"Decline a family invitation (signed in)","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/family-invitations/decline\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"security":[{"AccountCredential":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]{43}$"}},"additionalProperties":false}}}}}},"/api/family-invitations/preview":{"post":{"summary":"Preview a family invitation","description":"Public. What the invitation is for while pending (who invited, the role, the children's first names, the masked address), and only its status and inviter otherwise, so the page can say expired, canceled, declined or accepted. Unknown tokens are 404.","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["invitation"],"properties":{"invitation":{"$ref":"#/components/schemas/FamilyInvitationPreview"}}}}}}}},"404":{"description":"Unknown token"},"429":{"description":"Too many failed lookups from this requester, or too many requests for this invitation"}},"x-auth":"Public: the invitation token in the body is the only credential. The parent site may call it with its catalog-only key (x-api-key), adding the visitor's address to X-Forwarded-For, which only that key is trusted to forward. Limited per requester (the signed-in account, else the client address) on lookups that find no invitation, checked before looking, and per invitation on links that do (hourly policy limits).","x-curl-examples":[{"title":"Preview a family invitation","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/family-invitations/preview\""}],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]{43}$"}},"additionalProperties":false}}}}}},"/api/family-invitations/decline":{"post":{"summary":"Decline a family invitation","description":"Public (the invitee may have no account). Declines a pending invitation and tells the owner (invitation_declined) once; declining again answers the same.","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["status"],"properties":{"status":{"const":"declined","type":"string"}}}}}}}},"404":{"description":"Unknown token"},"409":{"description":"No longer pending"},"429":{"description":"Too many failed lookups from this requester, or too many requests for this invitation"}},"x-auth":"Public: the invitation token in the body is the only credential. The parent site may call it with its catalog-only key (x-api-key), adding the visitor's address to X-Forwarded-For, which only that key is trusted to forward. Limited per requester (the signed-in account, else the client address) on lookups that find no invitation, checked before looking, and per invitation on links that do (hourly policy limits).","x-curl-examples":[{"title":"Decline a family invitation","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/family-invitations/decline\""}],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]{43}$"}},"additionalProperties":false}}}}}},"/api/family-invitations/request-new":{"post":{"summary":"Ask for a new invitation","description":"Public. For an expired, canceled or declined invitation: tells the owner (new_invite_requested) once per invitation; asking again answers the first requestedAt.","tags":["Internal family account"],"x-internal":true,"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","required":["requestedAt"],"properties":{"requestedAt":{"type":"string","format":"date-time"}}}}}}}},"404":{"description":"Unknown token"},"409":{"description":"Still pending, or accepted"},"429":{"description":"Too many failed lookups from this requester, or too many requests for this invitation"}},"x-auth":"Public: the invitation token in the body is the only credential. The parent site may call it with its catalog-only key (x-api-key), adding the visitor's address to X-Forwarded-For, which only that key is trusted to forward. Limited per requester (the signed-in account, else the client address) on lookups that find no invitation, checked before looking, and per invitation on links that do (hourly policy limits).","x-curl-examples":[{"title":"Ask for a new invitation","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/family-invitations/request-new\""}],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]{43}$"}},"additionalProperties":false}}}}}},"/api/hallpass/settings":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Read staff run settings","description":"What a full order may spend in this deployment (HALLPASS_STAFF_MAX_SPEND_CENTS). With `ordersEnabled` false, only rehearsals start.","responses":{"200":{"description":"The settings.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["settings"],"properties":{"settings":{"$ref":"#/components/schemas/HallPassRunSettings"}}}}},"example":{"success":true,"data":{"settings":{"spendCap":{"amountCents":10000,"currency":"USD"},"ordersEnabled":true,"accountsEnabled":false,"capabilities":null,"capabilitiesUnavailable":true}}}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","x-curl-examples":[{"title":"Read staff run settings","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/hallpass/settings\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/canonical/schema":{"get":{"summary":"Discover canonical columns and structured filter capabilities","description":"Internal product metadata for organization, activity and session reads and agent tools. Each entity table includes filterCapabilities with currently visible field types/operators, relation names and target kinds, predicate forms and bounded request limits. Hidden fields are omitted; preview metadata is limited to disclosed datasets and unmasked fields. Metadata is authoritative for the current viewer and must not be reused for a different access scope. This endpoint returns schema metadata, not listing facts or evidence of verified availability.","tags":["Canonical"],"x-internal":true,"x-auth":"Authenticated account or named integration key. Returns metadata for the current effective plan; configured preview sessions receive only disclosed metadata.","responses":{"200":{"description":"Success envelope containing tier-filtered entity tables and structured filter capabilities; preview metadata includes its sample limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalSchemaResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Preview disclosure is not configured."}},"x-curl-examples":[{"title":"Discover permitted filter fields and operators","code":"curl \"https://apis.schoolsoutapp.com/api/canonical/schema\" -H \"x-api-key: $SCHOOLS_OUT_API_KEY\""}]}},"/api/canonical/search/interpret":{"post":{"summary":"Interpret a plain-language catalog search","description":"Translate a submitted search into one useful supported canonical filter using GPT-6 Luna. Understandable requests receive a best-effort filter and usually no feedback. Optional feedback is reserved for an important note about a material interpretation assumption or approximation that changes expected results; routine defaults, normal interpretation, recaps, and generic caveats stay silent. Apply the ready filter without confirmation. Omitted constraints generally remain unrestricted, while missing necessary values may use reasonable defaults. Clarification is reserved for unintelligible or clearly misguided intent. An unrestricted request may return an id-present filter. The API validates every branch against the current account tier and preview disclosure before returning it; feedback never bypasses validation. The provider receives the search text, current UTC time, and permitted filter capabilities, never account credentials or catalog rows. Interpretation does not execute a data read. A clarification, malformed or disallowed provider output, or failure must preserve the caller's existing filters and results; never retry as an unrestricted or literal search. Apply a ready filter to list, facet, or map reads and reuse it for paging, history, and view changes. This uses catalog access/read allowances, not Ask questions or research reports.","tags":["Internal canonical queries"],"x-internal":true,"security":[{"AccountCredential":[]}],"x-auth":"Signed Intelligence session or current account credential with catalog access; enabled preview sessions retain their disclosed capabilities and search rate limit. Cookie requests require a session-bound CSRF token and trusted origin.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["kind","text"],"properties":{"kind":{"type":"string","enum":["organization","activity","session"]},"text":{"type":"string","minLength":1,"maxLength":2000,"description":"Trimmed plain-language search; at most 2000 UTF-16 code units. Camp/camps are synonyms for organization/organizations as entity nouns, not name or category constraints; explicit names such as Camp Fire retain their words. The requested kind remains the root scope."}}},"example":{"kind":"organization","text":"Organizations within 10 km of 47.6062, -122.3321"}}}},"responses":{"200":{"description":"A validated best-effort filter, with optional feedback, or a clarification requiring revised search text.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"oneOf":[{"type":"object","additionalProperties":false,"required":["status","kind","filter"],"properties":{"status":{"const":"ready"},"kind":{"type":"string","enum":["organization","activity","session"]},"filter":{"$ref":"#/components/schemas/CanonicalFilter"},"feedback":{"type":"string","minLength":1,"maxLength":2000,"pattern":"\\S","description":"Exceptional note about a material interpretation assumption or approximation that changes expected results, accompanying application of the ready filter without confirmation. Routine defaults, normal interpretation, recaps, and generic caveats stay silent. Nonblank and at most 2000 UTF-16 code units; omitted when there is no important note."}}},{"type":"object","additionalProperties":false,"required":["status","message"],"properties":{"status":{"const":"clarification"},"message":{"type":"string"}}}]}}}}}},"400":{"description":"Missing, repeated, unexpected, or invalid request input."},"401":{"description":"Authentication required."},"403":{"description":"Catalog, preview, or browser CSRF policy refuses this request."},"429":{"description":"Read allowance, preview search rate, or concurrent interpretation limit reached."},"502":{"description":"Provider failed or returned an invalid or disallowed interpretation; preserve existing filters."},"503":{"description":"Search interpretation is not configured."},"504":{"description":"Search interpretation exceeded its deadline; preserve existing filters."}},"x-curl-examples":[{"title":"Translate a coordinate-radius search using an account credential","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/canonical/search/interpret\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data '{\"kind\":\"organization\",\"text\":\"Organizations within 10 km of 47.6062, -122.3321\"}'"}]}},"/api/hallpass/runs/{runId}/activity":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Read a staff run's activity","description":"Returns up to 200 lines of the run's live log after `after`, oldest first: what OS Pilgrim's overlay showed while it worked, with the run's attached details redacted. Poll while the run is active and pass the returned `after` back. A run Hallpass has not acknowledged has no lines.","responses":{"200":{"description":"The run's lines after `after`.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["activity"],"properties":{"activity":{"$ref":"#/components/schemas/HallPassRunActivity"}}}}},"example":{"success":true,"data":{"activity":{"lines":[{"sequence":41,"code":"visible_browser_start","summary":"I’m finding the browser window and bringing it into view.","at":"2026-10-19T02:23:18Z"},{"sequence":42,"code":"action_verified","summary":"Action 2 verified by fresh application state","at":"2026-10-19T02:23:35Z"}],"after":42}}}}}},"400":{"description":"`after` is not a sequence of zero or more.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"after must be a sequence of zero or more.","data":null}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}},"404":{"description":"No run has this ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Run not found.","data":null}}}},"503":{"description":"Hallpass could not be read just now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"The answer could not be sent. Try again.","data":null}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","x-curl-examples":[{"title":"Read a staff run's activity","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/hallpass/runs/$RUN_ID/activity?after=0\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"after","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Return lines after this sequence."}]}},"/api/hallpass/runs/{runId}/screen":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Watch a staff run's browser","description":"Streams a live, masked view of the browser the run's executor drives, OS Pilgrim's for an Automatic run or the Native worker's for a Native checkout run, passed through unchanged by Hallpass. The body is `multipart/x-mixed-replace`, with the boundary in the `Content-Type` (`pilgrim-screen` from Pilgrim, `hallpass-screen` from the Native worker). Each part has its own `Content-Type` and `Content-Length`: an `image/jpeg` frame with `X-Frame-Width`, `X-Frame-Height` and `X-Frame-Captured-At` (Unix seconds) headers, or an `application/json` state `{\"state\": \"waiting\" | \"live\" | \"ended\", \"at\": <Unix seconds>}`; Native frames also carry `X-Frame-Masks` and `X-Frame-Large-Masks`. A state part comes first and on every change. The executor ends the stream after `ended`; a stream can also close without `ended`, as when the API restarts. While the run is active, reconnect after 3 seconds. The executor masks private regions in every frame and never withholds one; a frame it cannot verify is masked whole.","responses":{"200":{"description":"The live view, until the executor ends it or the client disconnects. Disconnecting stops the stream at Hallpass and the executor too.","headers":{"Cache-Control":{"schema":{"type":"string"}},"X-Accel-Buffering":{"schema":{"const":"no"}}},"content":{"multipart/x-mixed-replace":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for signed-in staff.","data":null}}}},"404":{"description":"No run has this ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Run not found.","data":null}}}},"409":{"description":"Nothing to watch now: Hallpass has not acknowledged the run, the run is not active, or its executor has nothing to show (no Pilgrim task Pilgrim holds, or no Native checkout lease the worker admits). Retry after 3 seconds while the run is active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"This run has nothing to watch right now.","data":null}}}},"429":{"description":"Too many people are watching this run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Too many people are watching this run. Try again shortly.","data":null}}}},"503":{"description":"Hallpass or the executor could not be reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"The live view could not be opened just now.","data":null}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","x-curl-examples":[{"title":"Watch a staff run's browser","code":"curl -N \"https://apis.schoolsoutapp.com/api/hallpass/runs/$RUN_ID/screen\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --output run-screen.multipart"}],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/hallpass/runs/{runId}/trace":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Read a staff run's trace","description":"Returns up to 1,000 events about the run, oldest first, read from Axiom: OS Pilgrim's telemetry about the run's Pilgrim task (run-log lines, actions and their outcomes, helper operations with their phase timings, model calls, decisions and live-view states, each already redacted by Pilgrim), and Hallpass's and the API's own rows about the run (`kind` `hallpass`: creation, task submission, holds, decisions and answers, cancellations, stops that need a person, the ending), which carry ids, codes, states and amounts only. Pass the returned `after` back to read what followed; events at exactly that time are sent again, so clients de-duplicate on `id`. `complete` is false when a page was full. A 409 means tracing is not set up in this deployment. Until Hallpass names the Pilgrim task the page holds the run's own rows alone and `taskId` is absent; keep reading while the run is active.","responses":{"200":{"description":"The run's events after `after`.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["trace"],"properties":{"trace":{"$ref":"#/components/schemas/HallPassRunTrace"}}}}},"example":{"success":true,"data":{"trace":{"taskId":"13ea19eb-43f6-518e-bd68-61aee5a24bdb","source":"axiom","after":"2026-10-19T08:34:10Z","complete":true,"events":[{"id":"8a7f8702:412","at":"2026-10-19T08:33:36.26Z","launch":"8a7f8702","sequence":412,"kind":"action","name":"dispatching","code":"action_dispatching","step":5,"operation":"private_browser"},{"id":"8a7f8702:415","at":"2026-10-19T08:33:37.091Z","launch":"8a7f8702","sequence":415,"kind":"helper","name":"operatePrivateBrowser","outcome":"failed","code":"browser_search_not_focused","seconds":0.83,"phases":{"findingWindow":0.044,"readingControls":0.677,"starting":0.038},"attempt":1,"turn":"turn-3","counts":{"clicks":3,"refusedClicks":1}},{"id":"8a7f8702:431","at":"2026-10-19T08:33:59.517Z","launch":"8a7f8702","sequence":431,"kind":"helper","name":"clickBrowserControl","outcome":"failed","code":"pointer_target_unavailable","seconds":0.208,"value":"role=AXStaticText subrole=- actions=AXPress,AXScrollToVisible,AXShowMenu ancestors=AXList>AXComboBox>AXGroup>AXGroup listed=no"},{"id":"hallpass:0f3e2a1c-7d6b-4e5f-8a9b-1c2d3e4f5a6b:1792405450000000000","at":"2026-10-19T08:34:10Z","kind":"hallpass","name":"decision_answered","outcome":"accept","code":"confirm_total","value":"decision=4c1d8e2f-9a7b-5c3d-8e6f-2a1b3c4d5e6f option=confirm","runId":"0f3e2a1c-7d6b-4e5f-8a9b-1c2d3e4f5a6b","decidedBy":"person","sender":"apis"}]}}}}}},"400":{"description":"`after` is not an RFC 3339 time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"after must be an RFC 3339 time, or empty.","data":null}}}},"403":{"description":"The caller is not a staff or ADMIN account acting as itself, signed in or with its own key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Staff runs are for staff, signed in or with their own API key.","data":null}}}},"404":{"description":"No run has this ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"Run not found.","data":null}}}},"409":{"description":"Tracing is not set up in this deployment, or the run has no Pilgrim task yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"This run has nothing to trace yet.","data":null}}}},"503":{"description":"Hallpass or Axiom could not be read just now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"},"example":{"error":true,"message":"The trace could not be read just now.","data":null}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","x-curl-examples":[{"title":"Read a staff run's trace","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/hallpass/runs/$RUN_ID/trace?after=\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"runId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"after","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Return events at or after this time, the `after` of the previous page. Empty for the first page."}]}},"/api/ingestion/crawler-campaigns/{id}/urls/{url_id}":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Read saved URL classification details","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Read one saved URL belonging to the current campaign generation in data.url, including confidence_threshold from the same snapshot as its projected verdict, full source analysis, named organizations, offerings, rejection reasons, evidence and model provenance. Never acquires pages or calls models.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope."},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"404":{"description":"Campaign or saved URL not found."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"url_id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}}],"x-curl-examples":[{"title":"Read saved URL classification details","code":"curl -X GET \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/1/urls/1\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-campaigns/{id}/performance":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Read mass-crawl worker performance","description":"Worker-owned route: send directly to the exact HTTPS origin returned by POST /api/ingestion/crawler-worker/access. Both ordinary and mass-crawl state lives in the worker SQLite database. The central API returns authenticated 503 for this path. Read data.performance.workers: bounded latest profiles for shared mass-crawl workers. Each worker has worker_id, observed_at, stale and profile. Profile contains started_at, observed_at, running, stages (stage, count, error_count, total_ms, max_ms, active, oldest_active_ms), slots (search, crawl, classify), and optional resources (cpu_cores, memory_bytes, tasks). Timings are elapsed time, not CPU time. Worker stages may include other campaigns. This operator-only read never starts work. Optional models contains provider/model token usage (responses, input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens). Optional stage cancelled_count separates cancellation from failure. Timings include browser, captcha, parser, model and coordination sub-stages. Completed browser lifecycle phases have no live active count. Nested timings overlap.","x-auth":"Origin- and worker-bound Bearer capability. No cookies, x-api-key, redirects or token query parameters. Mutations require a writable capability; SSE streams end at capability expiry.","security":[{"CrawlerCapability":[]}],"responses":{"200":{"description":"Saved campaign state in the standard success/data envelope.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["performance"],"properties":{"performance":{"$ref":"#/components/schemas/CrawlerCampaignPerformance"}},"additionalProperties":false}},"additionalProperties":false}}}},"400":{"description":"Invalid input."},"401":{"description":"Missing, expired, wrong-origin or wrong-worker capability."},"403":{"description":"Staff or ADMIN required."},"404":{"description":"Campaign or saved URL not found."},"409":{"description":"Conflicting generation or idempotency identity."},"503":{"description":"This path was called on the central API. Acquire access and contact the worker directly."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}}],"x-curl-examples":[{"title":"Read mass-crawl worker performance","code":"curl -X GET \"$CRAWLER_WORKER_ORIGIN/api/ingestion/crawler-campaigns/1/performance\" \\\n  -H \"Authorization: Bearer $CRAWLER_ACCESS_TOKEN\" -H \"Origin: $INTELLIGENCE_ORIGIN\""}],"servers":[{"url":"https://{workerHost}","description":"Use the exact origin from the central access response, not an arbitrary host.","variables":{"workerHost":{"default":"worker.example.ts.net","description":"Trusted worker HTTPS tailnet hostname from the access response."}}}]}},"/api/ingestion/crawler-worker/status":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Read crawler worker connectivity and reported totals","description":"Reads the latest count and job summary snapshot from API memory, hydrating the durable cache once after restart. Durable summaries are replaced at most every five minutes, while live worker count updates arrive every few seconds. Preserves actual observed_at, received_at, and persisted_at; source identifies live, restored cache, or none. Publisher reconnects preserve the last snapshot. Current workers include all ordinary and mass job summaries; legacy capped snapshots explicitly have jobs_complete=false. Details, queues, evidence, and controls remain on the worker.","x-auth":"Current staff or ADMIN account; cookie mutations require CSRF and a trusted browser Origin.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Standard success/data response.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/CrawlerWorkerStatus"}},"required":["success","data"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid credential."},"403":{"description":"Account or origin is not permitted."},"503":{"description":"The initial durable cache read is unavailable; no fabricated empty snapshot is returned."}},"x-curl-examples":[{"title":"Read reported totals","code":"curl \"https://apis.schoolsoutapp.com/api/ingestion/crawler-worker/status\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/ingestion/crawler-worker/access":{"post":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Issue short-lived direct worker access","description":"Returns the exact HTTPS tailnet origin and an origin- and worker-bound capability. The browser sends detailed crawler requests directly to that origin, with credentials omitted and Authorization: Bearer. Keep the token in memory, refresh before expiry and bind caches to worker ID plus origin. Read-only previews request read_only:true; loopback origins are forced read-only. Losing a connection does not stop work, and clients must never automatically replay writes.","x-auth":"Current staff or ADMIN account; cookie mutations require CSRF and a trusted browser Origin.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Standard success/data response.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/CrawlerWorkerAccess"}},"required":["success","data"],"additionalProperties":false}}}},"400":{"description":"Unexpected fields or non-boolean read_only."},"401":{"description":"Missing or invalid credential."},"403":{"description":"Account or origin is not permitted."},"503":{"description":"Worker access has not been configured."}},"x-curl-examples":[{"title":"Issue a scoped read capability","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/crawler-worker/access\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"Origin: $INTELLIGENCE_ORIGIN\" -H \"Content-Type: application/json\" -d '{\"read_only\":true}'"}],"parameters":[{"in":"header","name":"Origin","required":true,"schema":{"type":"string","format":"uri"},"description":"Exact trusted Intelligence browser origin."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrawlerWorkerAccessRequest"}}}}}},"/api/internal/crawler-worker/session":{"post":{"tags":["Internal crawler publication"],"x-internal":true,"summary":"Open a crawler publisher session","description":"Starts a new authenticated worker publication session, fencing previous count and ready-URL sequences. Retains the last observed job summary and durable cache while awaiting the new publisher. Counts sequence restarts independently from the retained snapshot.","x-auth":"Dedicated crawler publisher Bearer credential; browser Origin requests are refused.","security":[{"CrawlerPublisher":[]}],"responses":{"200":{"description":"Standard success/data response.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/CrawlerWorkerPublisherSession"}},"required":["success","data"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid credential."},"403":{"description":"Account or origin is not permitted."}},"x-curl-examples":[{"title":"Open publisher session","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/internal/crawler-worker/session\" -H \"Authorization: Bearer $CRAWLER_SYNC_TOKEN\""}]}},"/api/internal/crawler-worker/counts":{"post":{"tags":["Internal crawler publication"],"x-internal":true,"summary":"Publish count-only NDJSON snapshots","description":"Consumes newline-delimited CrawlerWorkerCountSnapshot objects within 32 MiB per request. Every line requires the active session and a positive, strictly increasing sequence; observed_at cannot be more than one minute ahead or regress behind the last accepted observation. Each accepted line immediately replaces memory; a later invalid line does not roll back earlier lines. Complete snapshots support at most 100000 unique kind/ID pairs, preserve all ordinary and mass job summaries, and explicitly flag legacy incomplete payloads. The API broadcasts replacements through its staff-authenticated SSE route and persists at most every five minutes; a failed cache write retains live memory and retries on a later persistence interval. Details and evidence are never included.","x-auth":"Dedicated crawler publisher Bearer credential; browser Origin requests are refused.","security":[{"CrawlerPublisher":[]}],"responses":{"200":{"description":"Standard success/data response.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/CrawlerWorkerCountsAccepted"}},"required":["success","data"],"additionalProperties":false}}}},"400":{"description":"Invalid, interrupted, empty or oversized count stream."},"401":{"description":"Missing or invalid credential."},"403":{"description":"Account or origin is not permitted."},"409":{"description":"Expired publisher session or stale sequence."}},"x-curl-examples":[{"title":"Publish count snapshots","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/internal/crawler-worker/counts\" -H \"Authorization: Bearer $CRAWLER_SYNC_TOKEN\" -H \"Content-Type: application/x-ndjson\" --data-binary @count-snapshots.ndjson"}],"requestBody":{"required":true,"content":{"application/x-ndjson":{"schema":{"type":"string","description":"One CrawlerWorkerCountSnapshot JSON object per line. session and sequence are required on every publication.","x-record-schema":{"$ref":"#/components/schemas/CrawlerWorkerCountSnapshot"}},"example":"{\"session\":\"publisher-session\",\"sequence\":1,\"observed_at\":\"2026-10-19T12:00:00Z\",\"counts\":{\"jobs\":1},\"jobs\":[]}\n"}}}}},"/api/internal/crawler-worker/ready-urls":{"post":{"tags":["Internal crawler publication"],"x-internal":true,"summary":"Replace the ready homepage URL list","description":"Atomically replaces crawler_ready_urls with this complete URL-only snapshot. No job, evidence, classification or count fields are stored. URLs are validated, sorted and deduplicated; at most 500,000 URLs and 32 MiB are accepted. Reusing the same sequence with identical URLs is safe after a lost acknowledgement; older sequences or changed URLs under the same sequence return 409. An empty array explicitly clears the list.","x-auth":"Dedicated crawler publisher Bearer credential; browser Origin requests are refused.","security":[{"CrawlerPublisher":[]}],"responses":{"200":{"description":"Standard success/data response.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/CrawlerWorkerReadyAccepted"}},"required":["success","data"],"additionalProperties":false}}}},"400":{"description":"Invalid destination URLs, malformed snapshot or trailing JSON."},"401":{"description":"Missing or invalid credential."},"403":{"description":"Account or origin is not permitted."},"409":{"description":"Expired publisher session, stale sequence or sequence reused with different URLs."},"503":{"description":"The ready URL projection is unavailable."}},"x-curl-examples":[{"title":"Replace ready URLs","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/internal/crawler-worker/ready-urls\" -H \"Authorization: Bearer $CRAWLER_SYNC_TOKEN\" -H \"Content-Type: application/json\" --data-binary @ready-urls.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrawlerWorkerReadySnapshot"}}}}}},"/api/ingestion/crawler-worker/events":{"get":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Stream crawler totals and job summaries from the API","description":"Staff-authenticated server-sent events. Sends an immediate event named status containing a raw CrawlerWorkerStatus object (without the success/data wrapper), then complete latest replacements when live publication or persistence metadata changes. Sends heartbeat comments every 15 seconds and closes after five minutes; reconnect to receive the newest complete snapshot. Slow clients skip obsolete snapshots. No event IDs or replay are required, and subscribers never poll PostgreSQL. Keep the last observed snapshot visible with its timestamp during reconnects. Detailed state and controls remain on the worker.","x-auth":"Current staff or ADMIN account.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"SSE status events and heartbeat comments.","content":{"text/event-stream":{"schema":{"type":"string"},"example":"event: status\ndata: {\"worker_id\":\"frodo\",\"origin\":null,\"observed_at\":null,\"received_at\":null,\"persisted_at\":null,\"source\":\"none\",\"jobs_complete\":false,\"counts\":null,\"jobs\":[]}\n\n"}}},"401":{"description":"Missing or invalid credential."},"403":{"description":"Account is not permitted."},"503":{"description":"Initial cache load failed or stream capacity is full."}},"x-curl-examples":[{"title":"Stream reported crawler job summaries","code":"curl -N \"https://apis.schoolsoutapp.com/api/ingestion/crawler-worker/events\" -H \"Accept: text/event-stream\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/overseer/feedback":{"get":{"description":"Reads staff requests and review correction feedback across revisions, newest first. Stable opaque pagination preserves submitted history; entityConfigurationId filters one exact configuration; organizationId includes that camp and its activities and sessions. Both filters bind the cursor. Request targets resolve through current configuration ownership. Correction targets use the immutable reviewed version entity ID, with its saved name preferred over the current name. Parent organization context is derived for display. Request completion is explicitly agent-reported. Correction completion requires a successful applied version consuming the exact feedback ID and original version. Older feedback superseded by a newer request is labelled separately. No inference from delivery, transcript text, or an unrelated later version establishes completion.","parameters":[{"in":"query","name":"before","schema":{"type":"string"},"description":"Opaque exclusive nextBefore cursor bound to both target filters."},{"in":"query","name":"organizationId","schema":{"type":"string","format":"uuid"},"description":"Optional parent organization scope, including that organization and its activities and sessions."},{"in":"query","name":"entityConfigurationId","schema":{"type":"string","format":"uuid"},"description":"Optional exact configuration scope. Requests match the immutable supplied ID; correction versions resolve the original canonical entity configuration."},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":200},"description":"Maximum items returned, default 100."}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/OverseerFeedbackPage"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Recent feedback, explicit progress, and links to exact review outcomes."},"400":{"description":"Invalid cursor, organization ID, limit, or unsupported/repeated query."},"401":{"description":"Authentication required."},"403":{"description":"Current user-backed staff/ADMIN operator required; preview denied."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read recent Overseer feedback and request progress","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN identity. Account credentials and machine keys belonging to eligible users are accepted; a bare service credential is not. Intelligence uses its signed session cookie; cookie PUT requests require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"title":"Read recent feedback","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/feedback\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" --data-urlencode \"limit=20\""}],"x-internal":true}},"/api/ingestion/crawler-worker/control":{"post":{"tags":["Internal crawler monitoring"],"x-internal":true,"summary":"Request crawler pause or stop without tailnet access","description":"Staff-only removal of execution authority. An active mass-crawl pause includes its reported generation AND settings_revision; ordinary cancel targets its immutable job ID. Trusted non-loopback browser origin and cookie CSRF are required. Request IDs are idempotent while retained. The bounded in-memory queue expires after two minutes and is lost on API restart; pending/unconfirmed does not mean stopped. Frodo polls outbound HTTPS every two seconds and applies a fenced transition with a durable SQLite receipt. Status SSE delivers acknowledgement. No start, resume, arbitrary commands, or detailed data are relayed.","x-auth":"Current staff or ADMIN account; cookie mutations require CSRF and a trusted browser Origin.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Standard success/data response.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/CrawlerWorkerControlStatus"}},"required":["success","data"],"additionalProperties":false}}}},"400":{"description":"Invalid command or acknowledgement."},"401":{"description":"Missing or invalid credential."},"403":{"description":"Account or origin is not permitted."},"503":{"description":"Worker access has not been configured."},"409":{"description":"Worker/session, run, revision, or request ID conflict."},"429":{"description":"Recent control queue is full."}},"x-curl-examples":[{"title":"Request crawler pause or stop without tailnet access","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/ingestion/crawler-worker/control\" -H \"Content-Type: application/json\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"Origin: $INTELLIGENCE_ORIGIN\" --data-binary @crawler-control.json"}],"parameters":[{"in":"header","name":"Origin","required":true,"schema":{"type":"string","format":"uri"},"description":"Exact trusted Intelligence browser origin."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrawlerWorkerControlRequest"}}}}}},"/api/internal/crawler-worker/controls":{"post":{"tags":["Internal crawler publication"],"x-internal":true,"summary":"Exchange pending crawler controls and acknowledgements","description":"Dedicated worker-only authenticated HTTPS polling, independent of counts and ready-URL publication. Session-fenced requests acknowledge up to 16 durably committed receipts and receive up to 16 unexpired commands. A lost acknowledgement safely redelivers the same ID. The worker rechecks the immutable command and its run/settings fence before committing.","x-auth":"Dedicated crawler publisher Bearer credential; browser Origin requests are refused.","security":[{"CrawlerPublisher":[]}],"responses":{"200":{"description":"Standard success/data response.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/CrawlerWorkerControlDelivery"}},"required":["success","data"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid credential."},"403":{"description":"Account or origin is not permitted."},"400":{"description":"Invalid command or acknowledgement."},"409":{"description":"Worker/session, run, revision, or request ID conflict."},"429":{"description":"Recent control queue is full."}},"x-curl-examples":[{"title":"Exchange pending crawler controls and acknowledgements","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/internal/crawler-worker/controls\" -H \"Content-Type: application/json\" -H \"Authorization: Bearer $CRAWLER_SYNC_TOKEN\" --data-binary @crawler-control.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrawlerWorkerControlExchange"}}}}}},"/api/overseer/feedback/{kind}/{id}/decision":{"post":{"description":"Retry or cancel unfinished feedback under a current feedback timestamp. Retry accepts queued, in_progress, blocked, unknown and cancelled feedback; cancel accepts queued, in_progress, blocked and unknown. Completed and superseded feedback cannot be changed. A request retry preserves its immutable original and queues a new request with the same target and optional comments. A correction retry creates a new request_changes decision using the current review version. Cancellation does not mark an update reviewed or revert applied data. Decisions revoke active execution authority while retaining occupancy until the worker joins. The exact actor/requestId/input is persisted with its outcome; identical explicit replay returns that receipt. Only a committed exact-input rejection includes outcome=rejected and requestId; generic errors or reused IDs with different input do not resolve an uncertain command. Never automatically replay or rebase a failed mutation.","summary":"Retry or cancel Overseer feedback","parameters":[{"in":"path","name":"kind","required":true,"schema":{"type":"string","enum":["request","correction"]}},{"in":"path","name":"id","required":true,"schema":{"type":"string","description":"Request UUID or correction decision decimal ID from the feedback list."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverseerFeedbackDecisionInput"}}}},"responses":{"200":{"description":"Durable exact decision receipt. Refresh feedback to read the resulting work.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerFeedbackDecisionReceipt"}},"required":["success","data"]}}}},"400":{"description":"Invalid input or combined retry text exceeds its supported size. Only persisted exact-input rejections carry the terminal outcome marker.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerRejectedFeedbackDecision"},{"type":"object","not":{"required":["outcome"]},"properties":{"error":{"const":true},"message":{"type":"string"}},"required":["error","message"]}]}}}},"401":{"description":"Authentication required."},"403":{"description":"Current human account operator access is required; privileged machine keys and service-delegated accounts are denied. Cookie decisions also require a trusted origin and session-bound CSRF token."},"404":{"description":"The feedback item or its current target is missing. Only a durable rejection resolves an uncertain decision.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerRejectedFeedbackDecision"},{"type":"object","not":{"required":["outcome"]},"properties":{"error":{"const":true},"message":{"type":"string"}},"required":["error","message"]}]}}}},"409":{"description":"Feedback changed, action is no longer allowed, or the request identity was reused. Reused IDs with different input have no terminal marker.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerRejectedFeedbackDecision"},{"type":"object","not":{"required":["outcome"]},"properties":{"error":{"const":true},"message":{"type":"string"}},"required":["error","message"]}]}}}}},"security":[{"ApiKeyAuth":[]}],"tags":["Internal Overseer"],"x-auth":"Current staff or ADMIN human account using an account credential or the signed Intelligence cookie. Cookie decisions require a trusted origin and session-bound CSRF token. Machine credentials, service delegation, bare service credentials and preview viewers cannot make review decisions.","x-internal":true,"x-curl-examples":[{"title":"Retry feedback with additional guidance","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/feedback/request/$FEEDBACK_ID/decision\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"Content-Type: application/json\" --data '{\"requestId\":\"00000000-0000-4000-8000-000000000001\",\"expectedUpdatedAt\":\"2026-10-21T19:33:00Z\",\"action\":\"retry\",\"comments\":\"Check the current season calendar.\"}'"}]}},"/api/hallpass/nodes":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Read Pilgrim development node observations","description":"Signed-in staff only. Reads bounded A–D observations published over outbound HTTPS by the private manager. Missing, expired or post-restart observations remain unknown. No SSH runs in the API.","responses":{"200":{"description":"Four bounded node observations.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["nodes"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","checkedAt","connection","installed","running","telemetryFresh","installedCommit"],"properties":{"id":{"type":"string","enum":["pilgrim-a","pilgrim-b","pilgrim-c","pilgrim-d"]},"checkedAt":{"type":["string","null"],"format":"date-time"},"connection":{"type":"string","enum":["unknown","connected","unavailable"]},"installed":{"type":["boolean","null"]},"running":{"type":["boolean","null"]},"telemetryFresh":{"type":["boolean","null"]},"installedCommit":{"type":["string","null"]}}},"minItems":4,"maxItems":4}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"x-auth":"Current signed-in staff or ADMIN account acting as itself via identity.Resolver and StaffAccount(). Named machine/service/delegated credentials refused. Cookie mutations require trusted origin and fresh session-bound CSRF. Registered independently of Hallpass run enablement.","x-curl-examples":[{"title":"Read Pilgrim development node observations (review scope before executing)","code":"curl --config \"/private/intelligence-staff.curl\" --request GET \"https://apis.schoolsoutapp.com/api/hallpass/nodes\""}]}},"/api/internal/pilgrim/nodes":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Publish Pilgrim development node observations","description":"A single private manager uses a dedicated PILGRIM_FLEET_REPORT_TOKEN bearer credential of at least 32 characters. Browser origins, cookies, account and machine credentials do not authorize publication. At most 16384 bytes and exactly one sanitized row per A–D; future checks, checks older than five minutes and delayed/replayed observations are refused. Current display expires after 90 seconds. No installation or task controls. A restart clears the ephemeral report.","security":[{"PilgrimFleetReporter":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["schema","action","nodes"],"properties":{"schema":{"const":1},"action":{"const":"status"},"nodes":{"type":"array","minItems":4,"maxItems":4,"items":{"type":"object","additionalProperties":false,"required":["node","ok","checkedAt"],"properties":{"node":{"type":"string","enum":["pilgrim-a","pilgrim-b","pilgrim-c","pilgrim-d"]},"ok":{"type":"boolean"},"checkedAt":{"type":"string","format":"date-time"},"observedAt":{"type":["string","null"],"format":"date-time"},"installed":{"type":["boolean","null"]},"running":{"type":["boolean","null"]},"telemetryFresh":{"type":["boolean","null"]},"installedCommit":{"type":["string","null"],"pattern":"^[a-f0-9]{40}$"}}}}}}}}},"responses":{"200":{"description":"The complete report replaced the previous observation.","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["accepted"],"properties":{"accepted":{"const":true}}}}}}}},"400":{"description":"Invalid, over-age, oversized or non-allowlisted report."},"401":{"description":"Missing or invalid dedicated reporter credential."},"403":{"description":"Browser origin or cookies present."},"409":{"description":"An equal or newer observation is already held."}},"x-auth":"Dedicated reporter bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Publish Pilgrim development node observations (review scope before executing)","code":"curl --config \"/private/pilgrim-reporter.curl\" --request POST \"https://apis.schoolsoutapp.com/api/internal/pilgrim/nodes\" \\\n  --header \"Content-Type: application/json\" \\\n  --data-binary \"@/private/pilgrim-request.json\""}]}},"/api/internal/pilgrim/releases":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Prepare exact-main checked source release","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["releaseId","uploadPath","maximumBytes"],"properties":{"releaseId":{"type":"string"},"uploadPath":{"type":"string"},"maximumBytes":{"type":"integer"}}}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimRelease"}}}},"description":"Dedicated scoped bearer only. Origin and Cookie are refused. Never an account or general machine key.","x-auth":"Dedicated release publisher bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Prepare exact-main checked source release (review scope before executing)","code":"curl --config \"/private/pilgrim-release-publisher.curl\" --request POST \"https://apis.schoolsoutapp.com/api/internal/pilgrim/releases\" \\\n  --header \"Content-Type: application/json\" \\\n  --data-binary \"@/private/pilgrim-request.json\""}]}},"/api/internal/pilgrim/releases/{releaseId}/source":{"put":{"tags":["Internal Pilgrim source transport"],"x-internal":true,"summary":"Publish bounded private source and assign A canary","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["releaseId","generation"],"properties":{"releaseId":{"type":"string"},"generation":{"type":"string"}}}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"parameters":[{"in":"path","name":"releaseId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/x-git-bundle":{"schema":{"type":"string","format":"binary"}}}},"description":"Dedicated scoped bearer only. Origin and Cookie are refused. Never an account or general machine key.","x-auth":"Dedicated release publisher bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Publish bounded private source and assign A canary (review scope before executing)","code":"curl --config \"/private/pilgrim-release-publisher.curl\" --request PUT \"https://apis.schoolsoutapp.com/api/internal/pilgrim/releases/11111111-1111-4111-8111-111111111111/source\" \\\n  --header \"Content-Type: application/x-git-bundle\" \\\n  --data-binary \"@/private/source.bundle\""}]}},"/api/internal/pilgrim/updater/deployment":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Poll this node desired and active deployment","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/PilgrimDesired"}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"description":"Dedicated scoped bearer only. Origin and Cookie are refused. Never an account or general machine key.","x-auth":"Dedicated node updater bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Poll this node desired and active deployment (review scope before executing)","code":"curl --config \"/private/pilgrim-node-updater.curl\" --request GET \"https://apis.schoolsoutapp.com/api/internal/pilgrim/updater/deployment\""}]}},"/api/internal/pilgrim/updater/claim":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Claim or resume this node assignment","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/PilgrimClaim"}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimClaimRequest"}}}},"description":"Dedicated scoped bearer only. Origin and Cookie are refused. Never an account or general machine key.","x-auth":"Dedicated node updater bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Claim or resume this node assignment (review scope before executing)","code":"curl --config \"/private/pilgrim-node-updater.curl\" --request POST \"https://apis.schoolsoutapp.com/api/internal/pilgrim/updater/claim\" \\\n  --header \"Content-Type: application/json\" \\\n  --data-binary \"@/private/pilgrim-request.json\""}]}},"/api/internal/pilgrim/updater/progress":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Report fenced idempotent progress and installed truth","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/PilgrimProgressAck"}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimProgress"}}}},"description":"Dedicated scoped bearer only. Origin and Cookie are refused. Never an account or general machine key.","x-auth":"Dedicated node updater bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Report fenced idempotent progress and installed truth (review scope before executing)","code":"curl --config \"/private/pilgrim-node-updater.curl\" --request POST \"https://apis.schoolsoutapp.com/api/internal/pilgrim/updater/progress\" \\\n  --header \"Content-Type: application/json\" \\\n  --data-binary \"@/private/pilgrim-request.json\""}]}},"/api/internal/pilgrim/updater/bundle/{releaseId}":{"get":{"tags":["Internal Pilgrim source transport"],"x-internal":true,"summary":"Download this node assigned private source","responses":{"200":{"description":"Accepted result.","content":{"application/x-git-bundle":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"parameters":[{"in":"path","name":"releaseId","required":true,"schema":{"type":"string"}}],"description":"Dedicated scoped bearer only. Origin and Cookie are refused. Never an account or general machine key.","x-auth":"Dedicated node updater bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Download this node assigned private source (review scope before executing)","code":"curl --config \"/private/pilgrim-node-updater.curl\" --request GET \"https://apis.schoolsoutapp.com/api/internal/pilgrim/updater/bundle/11111111-1111-4111-8111-111111111111\""}]}},"/api/hallpass/pilgrim/deployments":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Read durable Pilgrim deployments and node progress","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/PilgrimFleet"}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"description":"Signed-in staff account only; mutations require existing cookie CSRF and origin checks. Explicit A-first canary controls.","x-auth":"Current signed-in staff or ADMIN account acting as itself via identity.Resolver and StaffAccount(). Named machine/service/delegated credentials refused. Cookie mutations require trusted origin and fresh session-bound CSRF. Registered independently of Hallpass run enablement.","x-curl-examples":[{"title":"Read durable Pilgrim deployments and node progress (review scope before executing)","code":"curl --config \"/private/intelligence-staff.curl\" --request GET \"https://apis.schoolsoutapp.com/api/hallpass/pilgrim/deployments\""}]},"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Create a new A-first deployment of a retained release","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["generation"],"properties":{"generation":{"type":"string"}}}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimCreateRequest"}}}},"description":"Signed-in staff account only; mutations require existing cookie CSRF and origin checks. Explicit A-first canary controls.","x-auth":"Current signed-in staff or ADMIN account acting as itself via identity.Resolver and StaffAccount(). Named machine/service/delegated credentials refused. Cookie mutations require trusted origin and fresh session-bound CSRF. Registered independently of Hallpass run enablement.","x-curl-examples":[{"title":"Create a new A-first deployment of a retained release (review scope before executing)","code":"curl --config \"/private/intelligence-staff.curl\" --request POST \"https://apis.schoolsoutapp.com/api/hallpass/pilgrim/deployments\" \\\n  --header \"Content-Type: application/json\" \\\n  --data-binary \"@/private/pilgrim-request.json\""}]}},"/api/hallpass/pilgrim/deployments/{generation}/promote":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Promote Pilgrim deployment intent","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["generation"],"properties":{"generation":{"type":"string"}}}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"parameters":[{"in":"path","name":"generation","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimActionRequest"}}}},"description":"Signed-in staff account only; mutations require existing cookie CSRF and origin checks. Explicit A-first canary controls.","x-auth":"Current signed-in staff or ADMIN account acting as itself via identity.Resolver and StaffAccount(). Named machine/service/delegated credentials refused. Cookie mutations require trusted origin and fresh session-bound CSRF. Registered independently of Hallpass run enablement.","x-curl-examples":[{"title":"Promote Pilgrim deployment intent (review scope before executing)","code":"curl --config \"/private/intelligence-staff.curl\" --request POST \"https://apis.schoolsoutapp.com/api/hallpass/pilgrim/deployments/42/promote\" \\\n  --header \"Content-Type: application/json\" \\\n  --data-binary \"@/private/pilgrim-request.json\""}]}},"/api/hallpass/pilgrim/deployments/{generation}/cancel":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Cancel Pilgrim deployment intent","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["generation"],"properties":{"generation":{"type":"string"}}}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"parameters":[{"in":"path","name":"generation","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimActionRequest"}}}},"description":"Signed-in staff account only; mutations require existing cookie CSRF and origin checks. Explicit A-first canary controls.","x-auth":"Current signed-in staff or ADMIN account acting as itself via identity.Resolver and StaffAccount(). Named machine/service/delegated credentials refused. Cookie mutations require trusted origin and fresh session-bound CSRF. Registered independently of Hallpass run enablement.","x-curl-examples":[{"title":"Cancel Pilgrim deployment intent (review scope before executing)","code":"curl --config \"/private/intelligence-staff.curl\" --request POST \"https://apis.schoolsoutapp.com/api/hallpass/pilgrim/deployments/42/cancel\" \\\n  --header \"Content-Type: application/json\" \\\n  --data-binary \"@/private/pilgrim-request.json\""}]}},"/api/users/viewer/groups":{"get":{"tags":["Internal family groups"],"x-internal":true,"summary":"List your groups","description":"Your groups, most recent activity first, each with its unread count and the newest message you can read.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["groups"],"properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/FamilyGroup"}}}}}}}}},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"List your groups","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/groups\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal family groups"],"x-internal":true,"summary":"Start a group","description":"Creates a group with you as its first member and invites the named friends. Only accepted friends can be invited, and a friend with a block with any member cannot; either answers 404 without saying which.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":80},"userIds":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":20,"description":"Accepted friends to invite; yourself and repeats are ignored."}},"additionalProperties":false}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["group"],"properties":{"group":{"$ref":"#/components/schemas/FamilyGroupDetail"}}}}}}}},"400":{"description":"Invalid name or userIds."},"404":{"description":"One of userIds is not your accepted friend."},"409":{"description":"You are in the most groups allowed (100), or the invitations exceed the group's 50 seats."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Start a group","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/groups\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Maya's 2nd grade class\",\"userIds\":[\"$FRIEND_ID\"]}'"}]}},"/api/users/viewer/groups/{groupId}":{"get":{"tags":["Internal family groups"],"x-internal":true,"summary":"Read a group","description":"The group with its members and open invitations.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["group"],"properties":{"group":{"$ref":"#/components/schemas/FamilyGroupDetail"}}}}}}}},"404":{"description":"Group not found, or you are not a member."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404.","x-curl-examples":[{"title":"Read a group","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"patch":{"tags":["Internal family groups"],"x-internal":true,"summary":"Rename a group","description":"Names the group again. Only the group's owner may rename it.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":80}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["group"],"properties":{"group":{"$ref":"#/components/schemas/FamilyGroupDetail"}}}}}}}},"400":{"description":"name is not one line of 1 to 80 characters."},"403":{"description":"You are a member but not the group's owner."},"404":{"description":"Group not found, or you are not a member."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404. Only its owner may make this change; another member receives 403.","x-curl-examples":[{"title":"Rename a group","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Sellwood neighbors\"}'"}]},"delete":{"tags":["Internal family groups"],"x-internal":true,"summary":"Delete a group","description":"Deletes the group for every member, with its messages, invitations and plans. Only the group's owner may delete it; any member may leave instead.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["deleted"],"properties":{"deleted":{"const":true}}}}}}}},"403":{"description":"You are a member but not the group's owner."},"404":{"description":"Group not found, or you are not a member."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404. Only its owner may make this change; another member receives 403.","x-curl-examples":[{"title":"Delete a group","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/groups/{groupId}/invitations":{"post":{"tags":["Internal family groups"],"x-internal":true,"summary":"Invite friends to a group","description":"Invites your accepted friends into a group you are a member of. A friend already a member or already invited is left as they are. Members and open invitations together take at most 50 seats.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userIds"],"properties":{"userIds":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":20,"description":"Accepted friends to invite; yourself and repeats are ignored.","minItems":1}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["group"],"properties":{"group":{"$ref":"#/components/schemas/FamilyGroupDetail"}}}}}}}},"400":{"description":"No userIds, or invalid ones."},"404":{"description":"Group not found, or one of userIds is not your accepted friend."},"409":{"description":"The group has no seat left."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404.","x-curl-examples":[{"title":"Invite friends to a group","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID/invitations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"userIds\":[\"$FRIEND_ID\"]}'"}]}},"/api/users/viewer/groups/{groupId}/membership":{"delete":{"tags":["Internal family groups"],"x-internal":true,"summary":"Leave a group","description":"Removes you from the group. The last member to leave deletes it with its messages and invitations.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["left"],"properties":{"left":{"const":true}}}}}}}},"404":{"description":"Group not found, or you are not a member."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404.","x-curl-examples":[{"title":"Leave a group","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID/membership\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/groups/{groupId}/messages":{"get":{"tags":["Internal family groups"],"x-internal":true,"summary":"Read a group's messages","description":"A page of the messages you can read (those sent after you joined), oldest first. With after, the messages since that seq: poll it with latestSeq to see new messages and Assistant's replies. With before, the page before that seq. With neither, the newest page.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"after","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Return messages after this seq, oldest first."},{"name":"before","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Return the page before this seq."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/FamilyGroupMessages"}}}}}},"400":{"description":"Invalid after, before or limit, or both after and before."},"404":{"description":"Group not found, or you are not a member."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404.","x-curl-examples":[{"title":"Read a group's messages","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID/messages?after=$LATEST_SEQ\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal family groups"],"x-internal":true,"summary":"Send a message to a group","description":"Stores the message as the group's next seq; sending the same clientMessageId again returns the stored message (200) instead of a second one. With asksAssistant (the app's mention of Assistant in the message), Assistant answers in the thread from the group's messages you can read and your own family context, which it never repeats to the group; its reply arrives as a later message with replyToMessageId. Asking Assistant requires your own session-backed account and is admitted before the message is stored. Verified parents share the private-chat daily account limit and all-account question budget; staff and ADMIN retain their exemption. Only one question per parent per group may be pending. Failed or duplicate sends and failed answers release the original admission once, including when the UTC day has changed. With shared, the message shares a collection you own or may open, or an activity the catalog shows, as a card; text is then an optional note. Members see the card only while they could open the item themselves.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["clientMessageId"],"properties":{"clientMessageId":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,64}$","description":"Your idempotency key for this message; keep it when retrying."},"text":{"type":"string","maxLength":4000,"description":"Required unless shared is given; then an optional note (and required when asksAssistant)."},"asksAssistant":{"type":"boolean","default":false,"description":"The message mentions Assistant, which answers in the thread."},"shared":{"type":"object","required":["kind","id"],"properties":{"kind":{"enum":["collection","activity"]},"id":{"type":"string","format":"uuid"}},"additionalProperties":false,"description":"An item to share as a card. Plans are shared by proposing them (POST .../plans)."}},"additionalProperties":false}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["message"],"properties":{"message":{"$ref":"#/components/schemas/FamilyGroupMessage"}}}}}}}},"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["message"],"properties":{"message":{"$ref":"#/components/schemas/FamilyGroupMessage"}}}}}}}},"400":{"description":"Invalid clientMessageId, text or shared."},"402":{"description":"The Ask allowance is spent (asksAssistant)."},"403":{"description":"Assistant requires the viewer’s own session-backed account and a verified email for consumer questions."},"404":{"description":"Group not found, or you are not a member; or the shared collection or activity is not one you may share."},"409":{"description":"clientMessageId was used for a different message, or Assistant is still answering your previous question in this group."},"429":{"description":"Daily message limit; retryAfterMs is how many milliseconds until another message is allowed. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs. Assistant’s daily account question limit returns Retry-After in seconds until the next UTC day."},"503":{"description":"Assistant is not configured, or the shared daily question budget is exhausted. Budget refusals include Retry-After in seconds until the next UTC day."},"401":{"description":"Current account required."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404.","x-curl-examples":[{"title":"Send a message to a group","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID/messages\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"clientMessageId\":\"m-8f3a2c1d\",\"text\":\"Anyone free on the 16th?\",\"asksAssistant\":false}'"}]}},"/api/users/viewer/groups/{groupId}/read":{"put":{"tags":["Internal family groups"],"x-internal":true,"summary":"Mark a group read","description":"Moves your read position up to seq, never past the newest message and never back.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["seq"],"properties":{"seq":{"type":"integer","minimum":0}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["lastReadSeq"],"properties":{"lastReadSeq":{"type":"integer","minimum":0}}}}}}}},"400":{"description":"seq is missing or negative."},"404":{"description":"Group not found, or you are not a member."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404.","x-curl-examples":[{"title":"Mark a group read","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID/read\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"seq\":42}'"}]}},"/api/users/viewer/group-invitations":{"get":{"tags":["Internal family groups"],"x-internal":true,"summary":"List your group invitations","description":"Your open invitations, newest first.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["invitations"],"properties":{"invitations":{"type":"array","items":{"$ref":"#/components/schemas/FamilyGroupInvitation"}}}}}}}}},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"List your group invitations","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/group-invitations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/group-invitations/{invitationId}":{"put":{"tags":["Internal family groups"],"x-internal":true,"summary":"Answer a group invitation","description":"accept joins the group (you read the messages sent after you join); decline closes the invitation. Accepting needs you to still be the inviter's friend with no block with any member. Repeating an answer already given is a no-op.","parameters":[{"name":"invitationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"enum":["accept","decline"]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["group"],"properties":{"group":{"oneOf":[{"$ref":"#/components/schemas/FamilyGroupDetail"},{"type":"null"}],"description":"The group after accepting; null after declining."}}}}}}}},"400":{"description":"Invalid action."},"404":{"description":"Invitation not found for this account."},"409":{"description":"The invitation was already answered the other way or can no longer be accepted, or you are in the most groups allowed."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin.","x-curl-examples":[{"title":"Answer a group invitation","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/group-invitations/$INVITATION_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"action\":\"accept\"}'"}]}},"/api/users/viewer/children/{childId}/documents":{"get":{"tags":["Internal family profiles"],"x-internal":true,"summary":"List a child's documents","description":"The documents on a child's profile, newest first, without their files.","parameters":[{"name":"childId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["documents"],"properties":{"documents":{"type":"array","items":{"$ref":"#/components/schemas/FamilyChildDocument"}}}}}}}}},"403":{"description":"Only the family's owner may manage a child's documents (manage_child_documents)."},"404":{"description":"Child not found for this account."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the family's owner (the parent admin) reads or changes a child's documents; co-parents and caregivers receive 403, other accounts 404.","x-curl-examples":[{"title":"List a child's documents","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/documents\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal family profiles"],"x-internal":true,"summary":"Add a document to a child's profile","description":"Body is the file: application/pdf, image/jpeg or image/png, at most 10 MiB, and its bytes must be that type. Images are re-encoded without their metadata (EXIF, including location); a PDF is kept as sent. The file is encrypted at rest, kept until you delete it, the child or the account, and never exported or shared. A child has at most 50 documents; the same file added again answers the stored document (200).","parameters":[{"name":"childId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"fileName","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200},"description":"The file's name; a path keeps its last part."},{"name":"title","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":120},"description":"What the document is; defaults to fileName."}],"requestBody":{"required":true,"content":{"application/pdf":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["document"],"properties":{"document":{"$ref":"#/components/schemas/FamilyChildDocument"}}}}}}}},"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["document"],"properties":{"document":{"$ref":"#/components/schemas/FamilyChildDocument"}}}}}}}},"400":{"description":"fileName is missing or invalid, or title is invalid."},"403":{"description":"Only the family's owner may manage a child's documents (manage_child_documents)."},"404":{"description":"Child not found for this account."},"409":{"description":"The child has 50 documents."},"413":{"description":"Larger than 10 MiB."},"415":{"description":"Not application/pdf, image/jpeg or image/png."},"422":{"description":"The bytes are not the declared type, or the image cannot be decoded (or is over 4096 px a side)."},"503":{"description":"Document storage is not configured (API_AES_KEY)."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the family's owner (the parent admin) reads or changes a child's documents; co-parents and caregivers receive 403, other accounts 404.","x-curl-examples":[{"title":"Add a document to a child's profile","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/documents?fileName=immunizations.pdf&title=Immunization%20record\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/pdf\" \\\n  --data-binary @immunizations.pdf"}]}},"/api/users/viewer/children/{childId}/documents/{documentId}":{"get":{"tags":["Internal family profiles"],"x-internal":true,"summary":"Download a child's document","description":"The file as an attachment, never cached.","parameters":[{"name":"childId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"documentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The file.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"Only the family's owner may manage a child's documents (manage_child_documents)."},"404":{"description":"Child or document not found for this account."},"503":{"description":"Document storage is not configured (API_AES_KEY)."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the family's owner (the parent admin) reads or changes a child's documents; co-parents and caregivers receive 403, other accounts 404.","x-curl-examples":[{"title":"Download a child's document","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/documents/$DOCUMENT_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -o document"}]},"delete":{"tags":["Internal family profiles"],"x-internal":true,"summary":"Delete a child's document","description":"Removes the document and its file for good.","parameters":[{"name":"childId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"documentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["deleted"],"properties":{"deleted":{"const":true}}}}}}}},"403":{"description":"Only the family's owner may manage a child's documents (manage_child_documents)."},"404":{"description":"Child or document not found for this account."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the family's owner (the parent admin) reads or changes a child's documents; co-parents and caregivers receive 403, other accounts 404.","x-curl-examples":[{"title":"Delete a child's document","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/children/$CHILD_ID/documents/$DOCUMENT_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/organizations/score-methodology":{"get":{"summary":"Read the organization scoring methodology","description":"Returns the running organization rubric: factors, weights, indicator formulas and examples, canonical input derivations, field decay, source authority, score limits, overall calculation, confidence range, bands, and interpretation notes. Numeric policy and examples share the scoring engine definitions. This endpoint reads no organization records and has no paid-plan gate.","tags":["Internal scoring methodology"],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"The running organization scoring methodology.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScoringMethodologyResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The catalog-only consumer site credential cannot read this authenticated route."}},"x-auth":"An authenticated account cookie, account credential, permitted machine key, or investor preview session. Available at every plan tier; no organization data or score store is read.","x-curl-examples":[{"title":"Read organization scoring methodology","code":"curl \"https://apis.schoolsoutapp.com/api/organizations/score-methodology\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_API_KEY\""}],"x-internal":true}},"/openapi/scoring.json":{"get":{"summary":"Read the scoring methodology OpenAPI reference","description":"Returns the standalone OpenAPI document for the organization scoring methodology API and its referenced schemas.","tags":["API references"],"security":[],"responses":{"200":{"description":"scoring methodology OpenAPI reference.","content":{"application/json":{"schema":{"type":"object","required":["openapi","info","paths"],"properties":{"openapi":{"type":"string"},"info":{"type":"object"},"paths":{"type":"object"},"components":{"type":"object"}}}}}}},"x-auth":"No key required","x-curl-examples":[{"title":"Read the scoring methodology OpenAPI reference","code":"curl \"https://apis.schoolsoutapp.com/openapi/scoring.json\""}]}},"/openapi/scoring.ts":{"get":{"summary":"Read the generated scoring methodology TypeScript types","description":"Returns the generated TypeScript declarations for the organization scoring methodology API.","tags":["API references"],"security":[],"responses":{"200":{"description":"Generated scoring methodology TypeScript declarations.","content":{"text/plain":{"schema":{"type":"string"}}}}},"x-auth":"No key required","x-curl-examples":[{"title":"Read the scoring methodology TypeScript types","code":"curl \"https://apis.schoolsoutapp.com/openapi/scoring.ts\""}]}},"/api/hallpass/pilgrim/management":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Read node host, login, runtime and recovery history","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/PilgrimManagementFleet"}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"description":"Signed-in staff only. Mutations require the existing origin and CSRF checks. One unresolved operation per node; retries reuse the same requestId. State is an observation, and older than 45 seconds is unknown.","x-auth":"Current signed-in staff or ADMIN account acting as itself via identity.Resolver and StaffAccount(). Named machine/service/delegated credentials refused. Cookie mutations require trusted origin and fresh session-bound CSRF. Registered independently of Hallpass run enablement.","x-curl-examples":[{"title":"Read node host, login, runtime and recovery history (review scope before executing)","code":"curl --config \"/private/intelligence-staff.curl\" --request GET \"https://apis.schoolsoutapp.com/api/hallpass/pilgrim/management\""}]}},"/api/hallpass/pilgrim/nodes/{nodeId}/operations":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Request a bounded Pilgrim restart or Mac reboot","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/PilgrimNodeOperation"}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"description":"Signed-in staff only. Mutations require the existing origin and CSRF checks. One unresolved operation per node; retries reuse the same requestId. State is an observation, and older than 45 seconds is unknown.","x-auth":"Current signed-in staff or ADMIN account acting as itself via identity.Resolver and StaffAccount(). Named machine/service/delegated credentials refused. Cookie mutations require trusted origin and fresh session-bound CSRF. Registered independently of Hallpass run enablement.","x-curl-examples":[{"title":"Request a bounded Pilgrim restart or Mac reboot (review scope before executing)","code":"curl --config \"/private/intelligence-staff.curl\" --request POST \"https://apis.schoolsoutapp.com/api/hallpass/pilgrim/nodes/pilgrim-a/operations\" --header \"Content-Type: application/json\" --data-binary @/private/pilgrim-request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimNodeOperationRequest"}}}},"parameters":[{"name":"nodeId","in":"path","required":true,"schema":{"type":"string","enum":["pilgrim-a","pilgrim-b","pilgrim-c","pilgrim-d"]}}]}},"/api/internal/pilgrim/management/report":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Report host state and reconcile this node recovery operation","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["operation"],"properties":{"operation":{"anyOf":[{"$ref":"#/components/schemas/PilgrimNodeOperation"},{"type":"null"}]}}}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"description":"Dedicated node bearer; no Origin or Cookie. Never replay an executing operation. A successful result requires observed new boot or launch plus a healthy runtime.","x-auth":"Dedicated node updater bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Report host state and reconcile this node recovery operation (review scope before executing)","code":"curl --config \"/private/pilgrim-node-updater.curl\" --request POST \"https://apis.schoolsoutapp.com/api/internal/pilgrim/management/report\" --header \"Content-Type: application/json\" --data-binary @/private/pilgrim-request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimManagementReport"}}}}}},"/api/internal/pilgrim/management/operations/{operationId}/claim":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Claim this node operation with a durable local attempt","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/PilgrimNodeOperation"}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"description":"Dedicated node bearer; no Origin or Cookie. Never replay an executing operation. A successful result requires observed new boot or launch plus a healthy runtime.","x-auth":"Dedicated node updater bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Claim this node operation with a durable local attempt (review scope before executing)","code":"curl --config \"/private/pilgrim-node-updater.curl\" --request POST \"https://apis.schoolsoutapp.com/api/internal/pilgrim/management/operations/11111111-1111-4111-8111-111111111111/claim\" --header \"Content-Type: application/json\" --data-binary @/private/pilgrim-request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimOperationClaimRequest"}}}},"parameters":[{"name":"operationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/internal/pilgrim/publisher":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Read local publisher and fleet state","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/PilgrimFleet"}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"description":"Dedicated scoped bearer only. Origin and Cookie are refused. Never an account or general machine key.","x-auth":"Dedicated release publisher bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Read local publisher and fleet state (review scope before executing)","code":"curl --config \"/private/pilgrim-release-publisher.curl\" --request GET \"https://apis.schoolsoutapp.com/api/internal/pilgrim/publisher\""}]}},"/api/internal/pilgrim/publisher/report":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Report metadata from the local build-once publisher","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["accepted"],"properties":{"accepted":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"description":"Dedicated scoped bearer only. Origin and Cookie are refused. Never an account or general machine key.","x-auth":"Dedicated release publisher bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Report metadata from the local build-once publisher (review scope before executing)","code":"curl --config \"/private/pilgrim-release-publisher.curl\" --request POST \"https://apis.schoolsoutapp.com/api/internal/pilgrim/publisher/report\" \\\n  --header \"Content-Type: application/json\" \\\n  --data-binary \"@/private/pilgrim-request.json\""}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimPublisherReport"}}}}}},"/api/internal/pilgrim/publisher/deployments/{generation}/promote":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Promote a fresh qualified A canary","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["accepted"],"properties":{"accepted":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"description":"Dedicated scoped bearer only. Origin and Cookie are refused. Never an account or general machine key.","x-auth":"Dedicated release publisher bearer only. Origin/Cookie refused; no account, general machine key, service key or other Pilgrim credential fallback. Use an owner-only curl config to keep the bearer out of process arguments.","x-curl-examples":[{"title":"Promote a fresh qualified A canary (review scope before executing)","code":"curl --config \"/private/pilgrim-release-publisher.curl\" --request POST \"https://apis.schoolsoutapp.com/api/internal/pilgrim/publisher/deployments/42/promote\" \\\n  --header \"Content-Type: application/json\" \\\n  --data-binary \"@/private/pilgrim-request.json\""}],"parameters":[{"in":"path","name":"generation","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimActionRequest"}}}}}},"/api/hallpass/pilgrim/publisher":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Pause or resume the local publisher","responses":{"200":{"description":"Accepted result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["paused"],"properties":{"paused":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid bounded request."},"401":{"description":"Credential missing or refused."},"403":{"description":"Scope refused."},"404":{"description":"Assignment or retained source missing."},"409":{"description":"State or idempotency conflict."},"422":{"description":"Provenance not verified."}},"description":"Signed-in staff account only; mutations require existing cookie CSRF and origin checks. Explicit A-first canary controls.","x-auth":"Current signed-in staff or ADMIN account acting as itself via identity.Resolver and StaffAccount(). Named machine/service/delegated credentials refused. Cookie mutations require trusted origin and fresh session-bound CSRF. Registered independently of Hallpass run enablement.","x-curl-examples":[{"title":"Pause or resume the local publisher (review scope before executing)","code":"curl --config \"/private/intelligence-staff.curl\" --request POST \"https://apis.schoolsoutapp.com/api/hallpass/pilgrim/publisher\" \\\n  --header \"Content-Type: application/json\" \\\n  --data-binary \"@/private/pilgrim-request.json\""}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PilgrimPublisherPauseRequest"}}}}}},"/api/hallpass/batches":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"List evaluation batches","description":"The newest 50 imports. Read-only stored state; does not dispatch or refresh provider work.","responses":{"200":{"description":"List evaluation batches","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["batches"],"properties":{"batches":{"type":"array","items":{"$ref":"#/components/schemas/HallPassBatch"}}}}}}}}},"400":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"401":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"403":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"409":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"503":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","x-curl-examples":[{"title":"List evaluation batches","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/hallpass/batches\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Queue an evaluation batch","description":"Atomically persist up to 1000 zero-spend evaluation rows from a UTF-8 CSV up to 1 MiB. Native is the default executor; explicit cua rows support checkout review only. Requires current broker support for each requested executor and boundary and explicit allowRegistrations for native free registration. No accounts, purchase, automatic decisions or fallback. Stable batch and row keys prevent duplicate work on import retry. Worker dispatch is independently enabled.","responses":{"200":{"description":"Queue an evaluation batch","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["batch"],"properties":{"batch":{"$ref":"#/components/schemas/HallPassBatch"}}}}}}}},"400":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"401":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"403":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"409":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"503":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"201":{"description":"New batch queued; nothing is submitted by this request.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["batch"],"properties":{"batch":{"$ref":"#/components/schemas/HallPassBatch"}}}}}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassBatchImport"}}}},"x-curl-examples":[{"title":"Queue an evaluation batch","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/hallpass/batches\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"Content-Type: application/json\" --data-binary @batch.json"}]}},"/api/hallpass/batches/preview":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Preview a CSV evaluation import","description":"Parse and fully validate without persistence, provider calls or submissions. Required columns external_id,start_url; optional instruction,boundary,executor,attendee_name,attendee_email,country,attendee_given_name,attendee_family_name,attendee_birth_date,attendee_grade,attendee_allergies,guardian_name,guardian_email,guardian_phone,guardian_given_name,guardian_family_name,deadline_minutes. Executor defaults to native; cua supports checkout_review only. Boundary defaults to checkout_review. Supplied attendee and guardian details are shared with the run. Grade and allergies become named answers; names, dates and contact details become separate attendee/guardian subjects. Problems must all be corrected before queueing. Configuration support is checked again at import.","responses":{"200":{"description":"Preview a CSV evaluation import","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["preview"],"properties":{"preview":{"$ref":"#/components/schemas/HallPassBatchPreview"}}}}}}}},"400":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"401":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"403":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"409":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"503":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassBatchPreviewInput"}}}},"x-curl-examples":[{"title":"Preview a CSV evaluation import","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/hallpass/batches/preview\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"Content-Type: application/json\" --data-binary @batch.json"}]}},"/api/hallpass/batches/{batchId}":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Read evaluation jobs","description":"Read up to 100 jobs after a position, with whole-batch counts. Uses stored run outcomes; no provider refresh or dispatch. A finished queue row is not evidence of booking success. Mode is null until actual executor selection.","responses":{"200":{"description":"Read evaluation jobs","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["batch","counts","jobs","nextAfter"],"properties":{"batch":{"$ref":"#/components/schemas/HallPassBatch"},"counts":{"$ref":"#/components/schemas/HallPassBatchCounts"},"jobs":{"type":"array","items":{"$ref":"#/components/schemas/HallPassBatchJob"}},"nextAfter":{"oneOf":[{"type":"integer"},{"type":"null"}]}}}}}}}},"400":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"401":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"403":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"409":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"503":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"404":{"description":"Batch not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","parameters":[{"name":"batchId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"after","in":"query","schema":{"type":"integer","minimum":0,"maximum":1000}}],"x-curl-examples":[{"title":"Read evaluation jobs","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/hallpass/batches/$BATCH_ID\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/hallpass/batches/{batchId}/configure":{"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Configure evaluation queue","description":"Set parallelism or pause/resume admissions. Cancellation is terminal and cancels queued rows only; already claimed/provider work may continue and must be followed through its existing run. Uncertain outcomes pause the batch for staff reconciliation.","responses":{"200":{"description":"Configure evaluation queue","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["batch"],"properties":{"batch":{"$ref":"#/components/schemas/HallPassBatch"}}}}}}}},"400":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"401":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"403":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"409":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"503":{"description":"Request refused, conflicting, or broker capabilities unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"404":{"description":"Batch not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}}},"security":[{"ApiKeyAuth":[]}],"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie, its account credential, or a named machine key owned by that staff account. The key may start, plan, read, follow, watch and cancel runs; only the signed-in account may answer decisions. Previews, bare service credentials and delegated service requests are rejected. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set; otherwise 404.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassBatchConfiguration"}}}},"parameters":[{"name":"batchId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"x-curl-examples":[{"title":"Configure evaluation queue","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/hallpass/batches/$BATCH_ID/configure\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"Content-Type: application/json\" --data-binary @batch.json"}]}},"/api/hallpass/cards":{"get":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"List saved payment cards","description":"Active saved cards, newest first, as display facts only. Registered only when saved cards are enabled.","responses":{"200":{"description":"The saved cards.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["cards"],"properties":{"cards":{"type":"array","items":{"$ref":"#/components/schemas/HallPassSavedCard"}}}}}}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}}},"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie or its account credential. Machine keys, previews, bare service credentials and delegated service requests are rejected: saving, listing and removing cards is a signed-in person's act. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set and HALLPASS_SAVED_CARDS=on with HALLPASS_SECRETS_KEY; otherwise 404.","x-curl-examples":[{"title":"List saved payment cards","code":"curl -X GET \"https://apis.schoolsoutapp.com/api/hallpass/cards\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Save a payment card","description":"Seals the card under HALLPASS_SECRETS_KEY and returns its display facts. The security code is never accepted. A card already saved is a 409.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassSavedCardInput"}}}},"responses":{"201":{"description":"The saved card.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["card"],"properties":{"card":{"$ref":"#/components/schemas/HallPassSavedCard"}}}}}}}},"400":{"description":"The card is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"409":{"description":"The same card is already saved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}}},"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie or its account credential. Machine keys, previews, bare service credentials and delegated service requests are rejected: saving, listing and removing cards is a signed-in person's act. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set and HALLPASS_SAVED_CARDS=on with HALLPASS_SECRETS_KEY; otherwise 404.","x-curl-examples":[{"title":"Save a payment card","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/hallpass/cards\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"content-type: application/json\" \\\n  --data '{\"number\":\"4242 4242 4242 4242\",\"expirationMonth\":8,\"expirationYear\":2029,\"cardholder\":\"Example Company\",\"postalCode\":\"94107\",\"label\":\"Operations\"}'"}]}},"/api/hallpass/cards/{cardId}":{"delete":{"tags":["Internal Hallpass runs"],"x-internal":true,"summary":"Remove a saved payment card","description":"Erases the sealed card at once; its display facts stay for receipts and the audit trail. Runs already paying with it fail closed at their next payment step.","parameters":[{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The card was erased.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","additionalProperties":false,"required":["erased"],"properties":{"erased":{"const":true}}}}}}}},"403":{"description":"The caller is not a signed-in staff or ADMIN account acting as itself.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}},"404":{"description":"No such active card.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HallPassRunError"}}}}},"x-auth":"Current staff or ADMIN account acting as itself: the Intelligence session cookie or its account credential. Machine keys, previews, bare service credentials and delegated service requests are rejected: saving, listing and removing cards is a signed-in person's act. Cookie mutations require a trusted origin and session-bound CSRF token. Registered only when HALLPASS_URL and HALLPASS_SERVICE_TOKEN are set and HALLPASS_SAVED_CARDS=on with HALLPASS_SECRETS_KEY; otherwise 404.","x-curl-examples":[{"title":"Remove a saved payment card","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/hallpass/cards/saved-card-0b26607e-3fcc-451d-b985-296d3dbeeeba\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/gmail/send":{"post":{"summary":"Send a parent-reviewed Mellow email","description":"Send the exact recipient, subject and plain text body the parent reviewed. Requires confirmed:true and a one-use draftId issued by Mellow’s draft_email tool for this adult and current Gmail connection. Fields may be edited in the review. Consumes the review before the provider send; never automatically retry a 502 or interrupted response. Check Gmail Sent before preparing a new review. No provider tokens are returned. There is no model send tool.","tags":["Internal Gmail"],"x-internal":true,"x-auth":"Own signed-in adult account with a live session; no bare account keys, service delegation or machine keys. Cookie requests require trusted origin and CSRF.","security":[{"AccountCredential":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["draftId","to","subject","body","confirmed"],"properties":{"draftId":{"type":"string","format":"uuid"},"to":{"type":"string","format":"email","maxLength":254},"subject":{"type":"string","minLength":1,"maxLength":300,"description":"One line; CR/LF and NUL are refused."},"body":{"type":"string","minLength":1,"maxLength":12000,"description":"Plain text, preserving the reviewed message exactly; NUL is refused."},"confirmed":{"const":true}}}}}},"responses":{"200":{"description":"Google confirmed the message was sent.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["sent","id","threadId"],"properties":{"sent":{"const":true},"id":{"type":"string"},"threadId":{"type":"string"}}}}}},"400":{"description":"Missing explicit confirmation or invalid reviewed message."},"401":{"description":"A signed-in adult account is required."},"403":{"description":"The credential cannot access this adult’s Gmail."},"409":{"description":"The review expired, was used, belongs to another adult, or Gmail was disconnected/reconnected. Read the message before attempting another review."},"501":{"description":"Gmail is not configured."},"502":{"description":"Google did not confirm the outcome. Do not retry automatically; check Gmail Sent before creating another draft."}},"x-curl-examples":[{"title":"Send the exact reviewed message once (replace the example draftId)","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/gmail/send\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"draftId\":\"00000000-0000-0000-0000-000000000000\",\"to\":\"camp@example.com\",\"subject\":\"Pickup question\",\"body\":\"What time is pickup?\",\"confirmed\":true}'"}]}},"/api/media/logos/{file}":{"get":{"tags":["Organization catalog"],"summary":"Read an organization logo","description":"A selected organization logo's public rendition, addressed by the SHA-256 of its bytes: catalog organization rows (logo_url), detail media (the selected logo's displayUrl) and family organization and host rows link here once the logo is rendered. Raster logos are re-encoded as PNG no larger than 512 pixels on the longer side, without metadata; SVG logos are rebuilt from an element and attribute allowlist (no scripts, event handlers, foreign content or external references) and served with a sandboxing Content-Security-Policy as a download when opened directly. The response never changes for a path, so it is cached publicly for a year as immutable, carries the hash as its ETag (If-None-Match answers 304), and is readable by every origin without credentials (Access-Control-Allow-Origin: *, Cross-Origin-Resource-Policy: cross-origin), including into a canvas. A changed logo gets a new path; a path stays served while any rendition holds its bytes, and stops when its uploaded logo is deleted. Only selected logos are published: other brand assets keep their protected URLs.","parameters":[{"name":"file","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{64}\\.(png|svg)$"},"description":"The rendition's lowercase hex SHA-256 and the extension of its type: png or svg."}],"responses":{"200":{"description":"The logo.","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"public, max-age=31536000, immutable"},"ETag":{"schema":{"type":"string"},"description":"The quoted SHA-256."},"Access-Control-Allow-Origin":{"schema":{"type":"string"},"description":"Always *."}},"content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/svg+xml":{"schema":{"type":"string","format":"binary"}}}},"304":{"description":"The logo named by If-None-Match; it never changes."},"404":{"description":"No rendition holds these bytes, or the extension is not the rendition's type."}},"security":[],"x-auth":"Public; anyone with the link.","x-curl-examples":[{"title":"Read an organization logo","code":"curl -O \"https://apis.schoolsoutapp.com/api/media/logos/$LOGO_SHA256.png\""}]}},"/api/onboarding/schools":{"get":{"tags":["Internal signed-out onboarding"],"x-internal":true,"summary":"Search schools for onboarding","description":"Finds schools by name (q, trigram word similarity), nearest a ZIP code or latitude,longitude (near), or both, in the family school search's order: name matches first by similarity, then nearest, then id. At most 10. Private, max-age=300.","security":[{"OnboardingKey":[]}],"x-auth":"Public: no account or x-api-key. Requires x-onboarding-key naming a key in ONBOARDING_PUBLIC_KEYS; with none configured the route answers 404. Limited to 60 requests a minute and 1,000 a day per client address.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"required":["schools"],"properties":{"schools":{"type":"array","maxItems":10,"items":{"$ref":"#/components/schemas/OnboardingSchool"}}}}}},"example":{"success":true,"data":{"schools":[{"id":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","name":"Lincoln Elementary School","area":"Portland, OR","postalCode":"97202","district":{"id":"4110040","name":"Portland Public Schools"},"point":{"latitude":45.4791,"longitude":-122.6482},"calendarStatus":"published"}]}}}}},"400":{"description":"Neither q nor near, q outside 2 to 80 characters, near neither a ZIP code nor latitude,longitude, or another query parameter."},"401":{"description":"An x-api-key that is not a valid credential: the server refuses an explicit invalid credential on every route, public ones included. Send no x-api-key."},"403":{"description":"x-onboarding-key is missing or not a configured key."},"404":{"description":"The onboarding routes are off: ONBOARDING_PUBLIC_KEYS is not set."},"429":{"description":"This client address has spent its minute or day. Retry-After and retryAfterMs say when to try again.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds."}}}},"x-curl-examples":[{"title":"Search schools for onboarding","code":"curl \"https://apis.schoolsoutapp.com/api/onboarding/schools?q=lincoln&near=97202\" \\\n  -H \"x-onboarding-key: $SCHOOLS_OUT_ONBOARDING_KEY\""}],"parameters":[{"in":"query","name":"q","schema":{"type":"string","minLength":2,"maxLength":80}},{"in":"query","name":"near","description":"A 5-digit ZIP code (its centre) or latitude,longitude. A ZIP code School's Out does not know orders by name alone.","schema":{"type":"string"}}]}},"/api/onboarding/places":{"get":{"tags":["Internal signed-out onboarding"],"x-internal":true,"summary":"Search places for onboarding","description":"Finds areas by a city, neighborhood, ZIP code or street address (q), with the family place search's geocoder and area names. At most 8. A street address answers with the area it is in and a point rounded to three decimals; no address is returned. Private, max-age=300.","security":[{"OnboardingKey":[]}],"x-auth":"Public: no account or x-api-key. Requires x-onboarding-key naming a key in ONBOARDING_PUBLIC_KEYS; with none configured the route answers 404. Limited to 60 requests a minute and 1,000 a day per client address.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"required":["places"],"properties":{"places":{"type":"array","maxItems":8,"items":{"$ref":"#/components/schemas/OnboardingPlace"}}}}}},"example":{"success":true,"data":{"places":[{"placeId":"op1.NDUuNDc5LC0xMjIuNjQ4","label":"Sellwood, Portland, OR","postalCode":"97202","region":"OR","point":{"latitude":45.479,"longitude":-122.648},"served":true}]}}}}},"400":{"description":"q missing or outside 2 to 80 characters, or another query parameter."},"401":{"description":"An x-api-key that is not a valid credential: the server refuses an explicit invalid credential on every route, public ones included. Send no x-api-key."},"403":{"description":"x-onboarding-key is missing or not a configured key."},"404":{"description":"The onboarding routes are off: ONBOARDING_PUBLIC_KEYS is not set."},"429":{"description":"This client address has spent its minute or day. Retry-After and retryAfterMs say when to try again.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds."}}},"503":{"description":"Place search is unavailable."}},"x-curl-examples":[{"title":"Search places for onboarding","code":"curl \"https://apis.schoolsoutapp.com/api/onboarding/places?q=Sellwood\" \\\n  -H \"x-onboarding-key: $SCHOOLS_OUT_ONBOARDING_KEY\""}],"parameters":[{"in":"query","name":"q","required":true,"schema":{"type":"string","minLength":2,"maxLength":80}}]}},"/api/onboarding/picks":{"post":{"tags":["Internal signed-out onboarding"],"x-internal":true,"summary":"Pick camps for onboarding","description":"Suggests listed activities with a session ahead within 10 miles of the area, for children of the given ages, read exactly as the parent site's signed-out catalog reads them (the family projection with no household). Ranked by how many children each fits by age and interest, then by age alone, then nearest, then activity id; the same request answers the same picks. total counts the activities in reach that fit an age. With schoolIds, each school's published days off for the next year (no school, breaks and holidays; at most 60 each). Nothing is stored. Not cached.","security":[{"OnboardingKey":[]}],"x-auth":"Public: no account or x-api-key. Requires x-onboarding-key naming a key in ONBOARDING_PUBLIC_KEYS; with none configured the route answers 404. Limited to 60 requests a minute and 1,000 a day per client address.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true,"type":"boolean"},"data":{"type":"object","additionalProperties":false,"required":["picks","total","area"],"properties":{"picks":{"type":"array","maxItems":8,"items":{"$ref":"#/components/schemas/OnboardingPick"}},"total":{"type":"integer","minimum":0},"area":{"type":"object","additionalProperties":false,"required":["label","point"],"properties":{"label":{"type":["string","null"],"description":"The area's name; null where School's Out's areas do not name it."},"point":{"$ref":"#/components/schemas/OnboardingPoint"}}},"schools":{"type":"array","maxItems":4,"description":"Present when schoolIds is.","items":{"type":"object","additionalProperties":false,"required":["id","daysOff"],"properties":{"id":{"type":"string","format":"uuid"},"daysOff":{"type":"array","maxItems":60,"items":{"type":"object","additionalProperties":false,"required":["date","kind","label"],"properties":{"date":{"type":"string","format":"date"},"kind":{"type":"string","enum":["no_school","break","holiday"]},"label":{"type":"string","description":"The break's name for a break day, else the day's label."}}}}}}}}}}},"example":{"success":true,"data":{"picks":[{"activityId":"0f2a8c3e-5b1d-4e7f-9a6c-3d8b2e1f4a50","organizationId":"7c1e9b2d-4a3f-4c8e-b5d6-1e2f3a4b5c6d","name":"Robotics Week","organizationName":"Sellwood Makers","logoUrl":"https://apis.schoolsoutapp.com/api/media/logos/sellwood-makers.png","imageUrl":"https://images.example.com/robotics.jpg","ages":{"min":7,"max":12},"nextSession":{"id":"5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d","start":"2026-11-23","end":"2026-11-25"},"priceText":"From $95 a day","point":{"latitude":45.4712,"longitude":-122.6531},"distanceMiles":0.6,"because":{"child":0,"interest":"robotics"}}],"total":37,"area":{"label":"Sellwood, Portland, OR","point":{"latitude":45.479,"longitude":-122.648}},"schools":[{"id":"9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34","daysOff":[{"date":"2026-11-26","kind":"holiday","label":"Thanksgiving"}]}]}}}}},"400":{"description":"An unknown or mistyped field, an area that names not exactly one of placeId, postalCode or point, a child without a birthday, age or grade, an age outside 0 to 17, more than 6 children, 10 interests a child or 20 in all, more than 4 schoolIds, or a limit outside 1 to 8."},"401":{"description":"An x-api-key that is not a valid credential: the server refuses an explicit invalid credential on every route, public ones included. Send no x-api-key."},"403":{"description":"x-onboarding-key is missing or not a configured key."},"404":{"description":"The onboarding routes are off (ONBOARDING_PUBLIC_KEYS is not set), or area.postalCode is a ZIP code School's Out does not know."},"429":{"description":"This client address has spent its minute or day. Retry-After and retryAfterMs say when to try again.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds."}}}},"x-curl-examples":[{"title":"Pick camps for onboarding","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/onboarding/picks\" \\\n  -H \"x-onboarding-key: $SCHOOLS_OUT_ONBOARDING_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"area\":{\"postalCode\":\"97202\"},\"children\":[{\"birthday\":\"2018-03-03\",\"interests\":[\"robotics\"]}]}'"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingPicksInput"},"example":{"area":{"postalCode":"97202"},"schoolIds":["9e4f7a21-3b6c-4d8e-a1f5-2c7b0d9e6f34"],"children":[{"birthday":"2018-03-03","interests":["robotics"]},{"age":5}]}}}}}},"/api/admin/booking-profile-permissions":{"post":{"summary":"Provision a named staff-assisted booking permission","description":"Requires a live ADMIN account session, never a machine key or service delegation. An expiry is required when granting. Revocation erases affected private snapshots in the same transaction; renewed permission cannot reactivate older snapshots.","tags":["Internal booking profiles"],"security":[{"AccountCredential":[]}],"x-auth":"Live account session required. Staff reads and acceptance also require operator identity and a separately provisioned named permission, plus current family grant and reuse. No ADMIN, staff, machine or delegated service bypass of family scope. Cookie requests require CSRF. Private responses are no-store.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"userId":{"type":"string","format":"uuid"},"expiresAt":{"type":"string","format":"date-time"},"revoke":{"type":"boolean"}},"required":["userId"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"properties":{"updated":{"type":"boolean"}},"required":["updated"]}},"required":["success","data"]}}}},"400":{"description":"Invalid reference, origin, field scope or request"},"401":{"description":"Authentication required"},"403":{"description":"Session or scoped permission unavailable"},"409":{"description":"Stale source, unavailable snapshot or idempotency conflict"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Profile sealing is not configured"}},"x-internal":true,"x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/admin/booking-profile-permissions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @request.json"}]}},"/api/staff/booking-profiles/preview":{"post":{"summary":"Preview authorized registration fields","description":"Resolves current eligible adults and child data, projects only authorized registration fields and records access before returning values. Does not expose source conversation references. Unsupported populated approved fields return 409 with value-free executionSupport metadata, preventing older clients from treating a successful projection as executable. A supported preview does not attest deployed executor or provider readiness.","tags":["Internal booking profiles"],"security":[{"AccountCredential":[]}],"x-auth":"Live account session required. Staff reads and acceptance also require operator identity and a separately provisioned named permission, plus current family grant and reuse. No ADMIN, staff, machine or delegated service bypass of family scope. Cookie requests require CSRF. Private responses are no-store.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"authorizationId":{"type":"string","format":"uuid"},"providerOrigin":{"type":"string"}},"required":["authorizationId","providerOrigin"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"properties":{"preview":{"$ref":"#/components/schemas/BookingProfilePreview"}},"required":["preview"]}},"required":["success","data"]}}}},"400":{"description":"Invalid reference, origin, field scope or request"},"401":{"description":"Authentication required"},"403":{"description":"Session or scoped permission unavailable"},"409":{"description":"Changed or unavailable source, or unsupported populated approved fields. Unsupported fields include value-free executionSupport metadata."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Profile sealing is not configured"}},"x-internal":true,"x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/staff/booking-profiles/preview\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @request.json"}]}},"/api/staff/booking-profiles/authorizations":{"post":{"summary":"Grant booking authorization on a family's behalf","description":"Named booking staff authorize one child, eligible guardian and exact HTTPS origin. Staff grants always share the complete scope (BookingProfileCompleteField); an explicit fields array must equal it. The guardian defaults to the owner and must remain eligible, and is recorded as grantor with separate staff attribution. Every request requires the current childVersion, including retries. Enabling reuse advances that version; after response loss re-read the child before retrying. Reuse requires an exact field set, guardian and grantor. An explicit expiresAt must match exactly; omission preserves an equivalent live grant expiring within 90 days, otherwise creates one for 90 days. The maximum lifetime is 365 days. Responses expose the actual scope, expiry and API transport support; this is not live executor readiness. Families can see and revoke these grants.","tags":["Internal booking profiles"],"security":[{"AccountCredential":[]}],"x-auth":"Live human account session, operator identity and a separately provisioned named permission required, the same gate as search. Machine and delegated service credentials are refused. Cookie requests require CSRF. Private responses are no-store.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"childId":{"type":"string","format":"uuid"},"childVersion":{"type":"integer","minimum":1},"providerOrigin":{"type":"string"},"guardianId":{"type":"string","format":"uuid"},"expiresAt":{"type":"string","format":"date-time"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/BookingProfileField"},"minItems":4,"uniqueItems":true,"description":"Optional. When sent it must be exactly the complete scope (BookingProfileCompleteField); any other scope is refused with 400. Omit it: staff grants always share the complete scope."}},"required":["childId","childVersion","providerOrigin"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"properties":{"authorization":{"$ref":"#/components/schemas/BookingProfileStaffAuthorization"}},"required":["authorization"]}},"required":["success","data"]}}}},"400":{"description":"Invalid reference, origin or expiry, or an ineligible guardian"},"401":{"description":"Authentication required"},"403":{"description":"Session or named staff permission unavailable"},"409":{"description":"The child changed or no longer exists"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Staff-assisted booking profiles are not enabled"}},"x-internal":true,"x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/staff/booking-profiles/authorizations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @request.json"}]}},"/api/staff/booking-profiles/authorizations/revoke":{"post":{"summary":"Revoke a booking authorization as staff","description":"Named booking staff revoke any grant at its current version, family- or staff-made. Its private snapshots are erased as with a family revoke; the staff member is attributed and audited.","tags":["Internal booking profiles"],"security":[{"AccountCredential":[]}],"x-auth":"Live human account session, operator identity and a separately provisioned named permission required, the same gate as search. Machine and delegated service credentials are refused. Cookie requests require CSRF. Private responses are no-store.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"authorizationId":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1}},"required":["authorizationId","version"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"properties":{"revoked":{"type":"boolean","const":true}},"required":["revoked"]}},"required":["success","data"]}}}},"400":{"description":"Invalid reference"},"401":{"description":"Authentication required"},"403":{"description":"Session or named staff permission unavailable"},"409":{"description":"The authorization changed or does not exist"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Staff-assisted booking profiles are not enabled"}},"x-internal":true,"x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/staff/booking-profiles/authorizations/revoke\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @request.json"}]}},"/api/staff/booking-profiles/search":{"post":{"summary":"Search child profiles","description":"Browses or searches child names across active accounts. Each child includes only currently authorized booking options for the requested provider; children without options remain discoverable. Search terms and field values are never written to the audit. Results are ordered by case-insensitive first and last name, then child ID. Pass nextCursor with the same query to continue; changed or deleted cursor profiles require restarting the search. Each child also lists its eligible guardians by ID and name, independent of provider, so staff can grant on the family's behalf; no guardian contact details are returned. Omit providerOrigin or send an empty string for discovery only; bookingOptions are then empty. A nonempty providerOrigin must be a valid HTTPS origin. Discovery alone does not authorize preview or booking.","tags":["Internal booking profiles"],"security":[{"AccountCredential":[]}],"x-auth":"Live human account session, operator identity and a separately provisioned named permission required. Discovery does not require a family grant; booking options and private preview retain current provider, family grant and reuse checks. Machine and delegated service credentials are refused. Cookie requests require CSRF. Private responses are no-store.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"providerOrigin":{"type":"string"},"query":{"type":"string","minLength":0,"maxLength":80,"description":"Empty browses all active profiles; nonempty searches require 2–80 characters."},"cursor":{"type":"string","format":"uuid","description":"The previous page nextCursor; omit on the first page."}},"required":["query"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"properties":{"profiles":{"type":"array","maxItems":25,"items":{"$ref":"#/components/schemas/BookingProfileCandidate"}},"nextCursor":{"type":"string","format":"uuid","description":"Present only when another page is available."}},"required":["profiles"]}},"required":["success","data"]}}}},"400":{"description":"Invalid reference, origin, field scope or request"},"401":{"description":"Authentication required"},"403":{"description":"Session or scoped permission unavailable"},"409":{"description":"Stale source, unavailable snapshot or idempotency conflict"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Profile sealing is not configured"}},"x-internal":true,"x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/staff/booking-profiles/search\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @request.json"}]}},"/api/users/viewer/booking-authorizations":{"get":{"summary":"List live booking authorizations","description":"Returns unexpired, unrevoked grants for children the viewer owns or co-parents as an assigned guardian, newest first, at most 200. grantedByStaff marks grants School's Out staff made on the family's behalf. Revoke any of them with POST /api/users/viewer/booking-authorizations/revoke. Available while booking profiles are disabled.","tags":["Internal booking profiles"],"security":[{"AccountCredential":[]}],"x-auth":"Live account session required. Only the family's own children; staff status grants no family access. Private responses are no-store.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"properties":{"authorizations":{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/BookingProfileViewerAuthorization"}}},"required":["authorizations"]}},"required":["success","data"]}}}},"401":{"description":"Authentication required"},"403":{"description":"Session unavailable"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"x-internal":true,"x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/users/viewer/booking-authorizations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SESSION_ID\""}]},"post":{"summary":"Authorize scoped staff-assisted booking","description":"A current family owner or co-parent grants one child, eligible guardian, HTTPS provider origin, closed field set and expiry. Staff status does not grant family authority. The scope is immutable; revoke and create a new grant to change it.","tags":["Internal booking profiles"],"security":[{"AccountCredential":[]}],"x-auth":"Live account session required. Staff reads and acceptance also require operator identity and a separately provisioned named permission, plus current family grant and reuse. No ADMIN, staff, machine or delegated service bypass of family scope. Cookie requests require CSRF. Private responses are no-store.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingProfileGrantInput"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"properties":{"authorizationId":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1}},"required":["authorizationId","version"]}},"required":["success","data"]}}}},"400":{"description":"Invalid reference, origin, field scope or request"},"401":{"description":"Authentication required"},"403":{"description":"Session or scoped permission unavailable"},"409":{"description":"Stale source, unavailable snapshot or idempotency conflict"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Profile sealing is not configured"}},"x-internal":true,"x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/booking-authorizations\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @request.json"}]}},"/api/users/viewer/booking-authorizations/revoke":{"post":{"summary":"Revoke staff-assisted booking authorization","description":"The family owner, grantor or eligible chosen co-parent revokes the grant and erases its private snapshots atomically. Minimal value-free receipts remain for recovery.","tags":["Internal booking profiles"],"security":[{"AccountCredential":[]}],"x-auth":"Live account session required. Staff reads and acceptance also require operator identity and a separately provisioned named permission, plus current family grant and reuse. No ADMIN, staff, machine or delegated service bypass of family scope. Cookie requests require CSRF. Private responses are no-store.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"authorizationId":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":1}},"required":["authorizationId","version"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"properties":{"revoked":{"type":"boolean"}},"required":["revoked"]}},"required":["success","data"]}}}},"400":{"description":"Invalid reference, origin, field scope or request"},"401":{"description":"Authentication required"},"403":{"description":"Session or scoped permission unavailable"},"409":{"description":"Stale source, unavailable snapshot or idempotency conflict"},"429":{"$ref":"#/components/responses/FamilyRequestLimit"},"503":{"description":"Profile sealing is not configured"}},"x-internal":true,"x-curl-examples":[{"code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/booking-authorizations/revoke\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SESSION_ID\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @request.json"}]}},"/api/staff/booking-profiles/readiness":{"get":{"summary":"Read current booking-profile admission","description":"Returns only the current deployment feature gate, effective named-staff permission and route admission. Available while the feature is disabled so clients can explain the blocked state. No profile values, grants or qualification claims are returned. Preview and booking still require a current eligible selection and exact family/provider authorization.","tags":["Internal booking profiles"],"security":[{"AccountCredential":[]}],"x-auth":"Live session-backed operator identity required. No machine, service or delegated access. The dedicated permission is reported, not required to read this metadata. Private responses are no-store.","responses":{"200":{"description":"Current admission metadata","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"properties":{"readiness":{"$ref":"#/components/schemas/BookingProfileReadiness"}},"required":["readiness"]}},"required":["success","data"]}}}},"401":{"description":"Authentication required"},"403":{"description":"Signed-in staff required"},"500":{"description":"Current permission could not be read; clients must show unavailable, not enabled"}},"x-internal":true,"x-curl-examples":[{"title":"Read current staff booking-profile admission","code":"curl --get \"https://apis.schoolsoutapp.com/api/staff/booking-profiles/readiness\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"X-Session-Id: $SESSION_ID\""}]}},"/api/overseer/campaigns/options":{"get":{"tags":["Internal Overseer"],"summary":"Read available campaign work and research fields","description":"Returns the field vocabulary accepted by actual research forms, grouped by listing kind. Media is an explicit selectable capability; canonicalField identifies atomic range fields.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignOptions"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Read available campaign work and research fields","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/campaigns/options\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/overseer/campaigns/regions":{"get":{"tags":["Internal Overseer"],"summary":"Search installed campaign regions","description":"Searches installed source polygons without requiring display geometry. Omit kind to search all supported types, ordered state, metro, county, city, zip, then neighborhood before pagination; codes order results within each type. Submit a returned exact datasetId, kind and code in the brief; no free-text geographic scope is inferred.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignRegionPage"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Search installed campaign regions","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/campaigns/regions\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  --data-urlencode \"q=Seattle\""}],"parameters":[{"name":"kind","in":"query","required":false,"description":"Optional boundary kind; omitted searches every supported type. Required with code.","schema":{"type":"string","enum":["state","metro","county","city","neighborhood","zip"]}},{"name":"q","in":"query","required":false,"description":"Literal region name or code query; required unless code is supplied.","schema":{"type":"string","maxLength":200}},{"name":"code","in":"query","required":false,"description":"Exact stable boundary code; requires kind.","schema":{"type":"string","maxLength":8000}},{"name":"datasetId","in":"query","required":false,"description":"Installed immutable dataset, omitted selects the active dataset.","schema":{"type":"string","maxLength":8000}},{"name":"limit","in":"query","required":false,"description":"","schema":{"type":"integer","minimum":1,"maximum":50}},{"name":"offset","in":"query","required":false,"description":"","schema":{"type":"integer","minimum":0,"maximum":10000}}]}},"/api/overseer/campaigns/preview":{"post":{"tags":["Internal Overseer"],"summary":"Count the current campaign scope","description":"Validates brief-level field capabilities, normalizes atomic range fields, and runs the authorized scope matcher against the current catalog. Returns the full matching listing counts; existing listings appear once across work types, while discovery remains open and does not invent records. This read-only preview does not persist an ID or timestamps and does not validate individual checklist members. Launch resolves the same scope again and atomically saves the campaign and checklist, so catalog changes can change the counts. Invalid brief inputs report the failed field and reason; diagnostics omit freeform brief text, listing names, and raw invalid identifiers.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignPreview"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Count the current campaign scope","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/campaigns/preview\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"brief":{"$ref":"#/components/schemas/OverseerCampaignBrief"}},"required":["brief"]}}}}}},"/api/overseer/campaigns":{"get":{"tags":["Internal Overseer"],"summary":"List finite Overseer campaigns","description":"Returns a bounded campaign page with the next exclusive before cursor. The active state includes queued, researching, needs_input and ready_for_review campaigns; needs_attention may overlap active.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignPage"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"List finite Overseer campaigns","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/campaigns\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"before","in":"query","required":false,"description":"","schema":{"type":"string","maxLength":8000}},{"name":"limit","in":"query","required":false,"description":"","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"q","in":"query","required":false,"description":"","schema":{"type":"string","maxLength":200}},{"name":"state","in":"query","required":false,"description":"","schema":{"type":"string","enum":["queued","researching","needs_input","ready_for_review","merge_preparing","merging","completed","cancelled","needs_attention","active","closed"]}},{"name":"entityConfigurationId","in":"query","required":false,"description":"Only campaigns whose persisted checklist contains this exact record configuration. Applied before paging.","schema":{"type":"string","format":"uuid"}}]},"post":{"tags":["Internal Overseer"],"summary":"Launch a campaign from its brief","description":"Accepts the immutable launch brief and resolves its currently authorized catalog scope at submission time, then atomically bulk saves the campaign and checklist. The resolved count may differ from preview. Retry an uncertain launch with the identical brief, actor and requestId to recover its durable result; a requestId cannot be reused with different input. Later brief edits or catalog changes do not change the saved retry result. Campaigns have independent state, pause controls, memory and finite work; they do not repeat. Typed terminal rejections include success:false, campaignOutcome=rejected, requestId and message.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaign"}},"required":["success","data"]}}}},"400":{"description":"A typed terminal rejection includes success:false, campaignOutcome=rejected, requestId and message. An identical retry returns the same durable rejection; generic errors remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignLaunchRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"A typed terminal rejection includes success:false, campaignOutcome=rejected, requestId and message. An identical retry returns the same durable rejection; generic errors remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignLaunchRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}},"409":{"description":"A typed terminal rejection includes success:false, campaignOutcome=rejected, requestId and message. An identical retry returns the same durable rejection; generic errors remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignLaunchRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}}},"x-curl-examples":[{"title":"Launch a campaign from its brief","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/campaigns\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"brief":{"$ref":"#/components/schemas/OverseerCampaignBrief"},"requestId":{"type":"string","format":"uuid"}},"required":["brief","requestId"]}}}}}},"/api/overseer/campaigns/{id}":{"get":{"tags":["Internal Overseer"],"summary":"Read a campaign","description":"Returns persisted brief, progress, reports and safe runtime observations. Native thread identifiers, checkpoints and execution authority remain private.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaign"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Read a campaign","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}}]}},"/api/overseer/campaigns/{id}/pause":{"post":{"tags":["Internal Overseer"],"summary":"Pause or resume a campaign","description":"Requires the reviewed campaign version. Pause is independent of the global Overseer pause; global pause always wins. Clearing memory preserves staged proposals and committed work and never resumes paused execution.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaign"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Pause or resume a campaign","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/pause\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"expectedRevision":{"type":"integer","minimum":0},"expectedPauseVersion":{"type":"integer","minimum":0},"paused":{"type":"boolean"}},"required":["expectedRevision","expectedPauseVersion","paused"]}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}}]}},"/api/overseer/campaigns/{id}/clear-memory":{"post":{"tags":["Internal Overseer"],"summary":"Clear campaign working memory","description":"Requires the reviewed campaign version. Pause is independent of the global Overseer pause; global pause always wins. Clearing memory preserves staged proposals and committed work and never resumes paused execution.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaign"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Clear campaign working memory","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/clear-memory\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"expectedRevision":{"type":"integer","minimum":0}},"required":["expectedRevision"]}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}}]}},"/api/overseer/campaigns/{id}/messages":{"post":{"tags":["Internal Overseer"],"summary":"Send campaign feedback or follow-up work","description":"Persists an idempotent campaign-local message. An active coordinator receives live input; idle execution resumes the same campaign round. Selected field and listing scope remain enforced.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerMessage"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Send campaign feedback or follow-up work","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/messages\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"expectedRevision":{"type":"integer","minimum":0},"requestId":{"type":"string","format":"uuid"},"text":{"type":"string","minLength":1,"description":"Nonblank request, at most 16384 UTF-8 bytes. Original text is retained in its durable receipt."},"entityConfigurationId":{"type":"string","format":"uuid","description":"Optional exact record configuration_id. Included in immutable request identity; resolved server-side to an organization, activity, or session."},"field":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,63}$","description":"Optional catalog field key the request is about, such as refund_policy, or media for the gallery, cover and logo. Requires entityConfigurationId and must be a field of the resolved record kind. Included in immutable request identity."}},"required":["expectedRevision","requestId","text"]}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}}]}},"/api/overseer/campaigns/{id}/checklist":{"get":{"tags":["Internal Overseer"],"summary":"Read a campaign checklist page","description":"Returns bounded persisted outcomes for the frozen deduplicated listing membership.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignChecklistPage"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Read a campaign checklist page","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/checklist\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}},{"name":"afterId","in":"query","required":false,"description":"","schema":{"type":"string","maxLength":8000}},{"name":"limit","in":"query","required":false,"description":"","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"q","in":"query","required":false,"description":"","schema":{"type":"string","maxLength":200}},{"name":"kind","in":"query","required":false,"description":"","schema":{"type":"string","enum":["organization","activity","session"]}},{"name":"outcome","in":"query","required":false,"description":"","schema":{"type":"string","enum":["pending","researching","unchanged","proposals","blocked","excluded"]}}]}},"/api/overseer/campaigns/{id}/events":{"get":{"description":"Reads campaign-local persisted events with the existing bounded directional event cursors. The optional view selects all persisted events (the default) or useful updates; updates removes routine runtime and delivery events before pagination while retaining failures, user messages, coordinator output, blocked outcomes, campaign lifecycle events, and unrecognized event kinds.","parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}},{"description":"Optional campaign Activity view. Defaults to all for compatibility. Updates filters known routine runtime and delivery noise before pagination.","in":"query","name":"view","schema":{"enum":["all","updates"],"default":"all","type":"string"}},{"description":"Default asc. Desc reads newest events first.","in":"query","name":"order","schema":{"enum":["asc","desc"],"type":"string"}},{"description":"Exclusive ascending cursor. Rejected when order=desc.","in":"query","name":"afterId","schema":{"minimum":0,"type":"integer"}},{"description":"Exclusive descending cursor. Only accepted with order=desc; omit or use 0 for the newest page.","in":"query","name":"beforeId","schema":{"minimum":0,"type":"integer"}},{"description":"Optional exact revision; omitted or 0 includes all revisions.","in":"query","name":"revision","schema":{"minimum":0,"type":"integer"}},{"description":"Maximum events returned, default 100.","in":"query","name":"limit","schema":{"maximum":200,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"oneOf":[{"not":{"required":["nextBeforeId"]},"required":["nextAfterId"]},{"not":{"required":["nextAfterId"]},"required":["nextBeforeId"]}],"properties":{"events":{"items":{"$ref":"#/components/schemas/OverseerEvent"},"type":"array"},"hasMore":{"type":"boolean"},"nextAfterId":{"description":"Present for default/asc order; use as the next afterId.","minimum":0,"type":"integer"},"nextBeforeId":{"description":"Present only for order=desc; use as the next beforeId.","minimum":0,"type":"integer"}},"required":["events","hasMore"],"type":"object"},"success":{"const":true}},"required":["data","success"],"type":"object"}}},"description":"Events in ascending ID order and their next cursor."},"400":{"description":"Unsupported, duplicate, malformed or out-of-range query parameter."},"401":{"description":"Authentication required."},"403":{"description":"Current identity is not a user-backed staff or ADMIN operator, or the browser mutation lacks a trusted origin and session-bound CSRF token."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read campaign audit events","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN identity. Account credentials and machine keys belonging to eligible users are accepted; a bare service credential is not. Intelligence uses its signed session cookie; cookie PUT requests require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/events\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read the newest audit events"}],"x-internal":true}},"/api/overseer/campaigns/{id}/results":{"get":{"tags":["Internal Overseer"],"summary":"Read staged campaign results","description":"Returns exact proposal heads and campaign-wide approval counts. Search matches the saved proposal label or campaign checklist name. Research outcome, operation and decision filters use the same membership as filter-based bulk decisions; selectionRevision fences changes to proposals, decisions, or checklist membership and outcomes.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignResultsPage"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Read staged campaign results","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/results\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}},{"name":"q","in":"query","required":false,"description":"","schema":{"type":"string","maxLength":500}},{"name":"kind","in":"query","required":false,"description":"","schema":{"type":"string","enum":["organization","activity","session"]}},{"name":"operation","in":"query","required":false,"description":"","schema":{"type":"string","enum":["create","update","remove"]}},{"name":"decision","in":"query","required":false,"description":"","schema":{"type":"string","enum":["included","excluded","revision_requested"]}},{"name":"limit","in":"query","required":false,"description":"","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"offset","in":"query","required":false,"description":"","schema":{"type":"integer","minimum":0,"maximum":10000}},{"name":"outcome","in":"query","required":false,"schema":{"type":"string","enum":["pending","researching","unchanged","proposals","blocked","excluded"],"description":"Research outcome of the campaign checklist entry. Proposal-only candidates have no outcome."}},{"name":"hasProposals","in":"query","required":false,"schema":{"type":"boolean","description":"Require at least one proposal, independent of research outcome. Filter-based selections always resolve proposals."}}]}},"/api/overseer/campaigns/{id}/results/{proposalId}":{"get":{"tags":["Internal Overseer"],"summary":"Inspect a staged campaign proposal","description":"Returns a staged proposal with the existing full review detail, captured evidence, versions, current data, media, and removal preview. Staged creations remain provisional until a merged publication.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignProposalDetail"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Inspect a staged campaign proposal","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/results/$PROPOSAL_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}},{"name":"proposalId","in":"path","required":true,"description":"Staged proposal UUID.","schema":{"type":"string","format":"uuid"}},{"name":"version","in":"query","required":false,"description":"","schema":{"type":"integer","minimum":1}}]}},"/api/overseer/campaigns/{id}/results/decision":{"post":{"tags":["Internal Overseer"],"summary":"Include, exclude, or request campaign result revisions","description":"Requires a direct staff or ADMIN account. Explicit immutable versions or the complete server-side matching query are applied atomically against expectedRound and selectionRevision. Query selection may exclude individual proposal IDs; a changed selection refuses the entire request. Retries preserve request identity. Revision requests require feedback.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignDecisionResult"}},"required":["success","data"]}}}},"400":{"description":"A typed terminal rejection of this exact request includes its requestId and explicit rejected outcome marker. Identical retry returns the same durable rejection; unknown transport or server failures remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignCommandRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"A typed terminal rejection of this exact request includes its requestId and explicit rejected outcome marker. Identical retry returns the same durable rejection; unknown transport or server failures remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignCommandRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}},"409":{"description":"A typed terminal rejection of this exact request includes its requestId and explicit rejected outcome marker. Identical retry returns the same durable rejection; unknown transport or server failures remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignCommandRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}}},"x-curl-examples":[{"title":"Include, exclude, or request campaign result revisions","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/results/decision\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"requestId":{"type":"string","format":"uuid"},"expectedRound":{"type":"integer","minimum":1},"selectionRevision":{"type":"integer","minimum":0},"selection":{"$ref":"#/components/schemas/OverseerCampaignResultSelection"},"decision":{"type":"string","enum":["included","excluded","revision_requested"]},"feedback":{"type":"string","maxLength":8000}},"required":["requestId","expectedRound","selectionRevision","selection","decision"]}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}}]}},"/api/overseer/campaigns/{id}/feedback":{"post":{"tags":["Internal Overseer"],"summary":"Send campaign feedback with listing and proposal notes","description":"Requires a direct staff or ADMIN account. Saves one idempotent campaign follow-up and optional immutable proposal-version revision notes atomically against the reviewed result selection. Paused campaigns retain feedback without resuming. Optional batches resolve complete filtered selections on the server under the same top-level selectionRevision. Conflicting notes for one proposal reject the request. Optional listingNotes targets exact checklist or proposal-only identities without changing proposal decisions or clearing blocker outcomes. Each is retained structurally in the request receipt and listing_feedback event. Feedback may reopen queued, researching, needs_input or ready_for_review campaigns; pause remains in force. Merge preparation, publication and closed campaigns reject feedback.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignDecisionResult"}},"required":["success","data"]}}}},"400":{"description":"A typed terminal rejection of this exact request includes its requestId and explicit rejected outcome marker. Identical retry returns the same durable rejection; unknown transport or server failures remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignCommandRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"A typed terminal rejection of this exact request includes its requestId and explicit rejected outcome marker. Identical retry returns the same durable rejection; unknown transport or server failures remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignCommandRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}},"409":{"description":"A typed terminal rejection of this exact request includes its requestId and explicit rejected outcome marker. Identical retry returns the same durable rejection; unknown transport or server failures remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignCommandRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}}},"x-curl-examples":[{"title":"Send campaign feedback with proposal notes","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/feedback\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"requestId":{"type":"string","format":"uuid"},"expectedRound":{"type":"integer","minimum":1},"selectionRevision":{"type":"integer","minimum":0},"text":{"type":"string","maxLength":8000},"notes":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignFeedbackNote"},"maxItems":10000},"batches":{"type":"array","items":{"$ref":"#/components/schemas/OverseerCampaignFeedbackBatch"},"maxItems":100},"listingNotes":{"type":"array","maxItems":10000,"items":{"$ref":"#/components/schemas/OverseerCampaignListingFeedbackNote"}}},"required":["requestId","expectedRound","selectionRevision","text","notes"]}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}}]}},"/api/overseer/campaigns/{id}/results/prepare":{"post":{"tags":["Internal Overseer"],"summary":"Prepare a campaign publication manifest","description":"Requires a direct staff or ADMIN account. Reconciles included staged changes against current canonical data and dependency closure without publishing. The persisted immutable manifest includes conflicts requiring operator attention and binds publication to exact versions and reviewed selection.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignMergeManifest"}},"required":["success","data"]}}}},"400":{"description":"A typed terminal rejection of this exact request includes its requestId and explicit rejected outcome marker. Identical retry returns the same durable rejection; unknown transport or server failures remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignCommandRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"A typed terminal rejection of this exact request includes its requestId and explicit rejected outcome marker. Identical retry returns the same durable rejection; unknown transport or server failures remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignCommandRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}},"409":{"description":"A typed terminal rejection of this exact request includes its requestId and explicit rejected outcome marker. Identical retry returns the same durable rejection; unknown transport or server failures remain ambiguous.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignCommandRejection"},{"type":"object","additionalProperties":false,"properties":{"message":{"type":"string"},"success":{"const":false},"error":{"const":true}},"required":["message"]}]}}}}},"x-curl-examples":[{"title":"Prepare a campaign publication manifest","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/results/prepare\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"requestId":{"type":"string","format":"uuid"},"expectedRound":{"type":"integer","minimum":1},"selectionRevision":{"type":"integer","minimum":0}},"required":["requestId","expectedRound","selectionRevision"]}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}}]}},"/api/overseer/campaigns/{id}/results/merge":{"post":{"tags":["Internal Overseer"],"summary":"Publish the prepared campaign changes","description":"Requires a direct staff or ADMIN account. Publishes the exact reviewed prepared manifest in one transaction. A stale proposal, selection, round, canonical expectation, or authority fails without partial canonical changes. Durable request replay returns the existing result after uncertain responses.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignMergeResult"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input. An exact durable rejection includes requestId, manifestId, and mergeOutcome=rejected; a generic failure does not prove publication outcome.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignMergeRejection"},{"type":"object","additionalProperties":false,"properties":{"success":{"const":false},"message":{"type":"string","maxLength":8000},"error":{"const":true}},"required":["message"]}]}}}},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required. An exact durable rejection includes requestId, manifestId, and mergeOutcome=rejected; a generic failure does not prove publication outcome.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignMergeRejection"},{"type":"object","additionalProperties":false,"properties":{"success":{"const":false},"message":{"type":"string","maxLength":8000},"error":{"const":true}},"required":["message"]}]}}}},"404":{"description":"Campaign or campaign item not found. An exact durable rejection includes requestId, manifestId, and mergeOutcome=rejected; a generic failure does not prove publication outcome.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignMergeRejection"},{"type":"object","additionalProperties":false,"properties":{"success":{"const":false},"message":{"type":"string","maxLength":8000},"error":{"const":true}},"required":["message"]}]}}}},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission. An exact durable rejection includes requestId, manifestId, and mergeOutcome=rejected; a generic failure does not prove publication outcome.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OverseerCampaignMergeRejection"},{"type":"object","additionalProperties":false,"properties":{"success":{"const":false},"message":{"type":"string","maxLength":8000},"error":{"const":true}},"required":["message"]}]}}}}},"x-curl-examples":[{"title":"Publish the prepared campaign changes","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/results/merge\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"requestId":{"type":"string","format":"uuid"},"manifestId":{"type":"string","format":"uuid"},"expectedRound":{"type":"integer","minimum":1},"selectionRevision":{"type":"integer","minimum":0}},"required":["requestId","manifestId","expectedRound","selectionRevision"]}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}}]}},"/api/overseer/campaigns/{id}/jobs":{"get":{"description":"Returns the bounded linked Trident and crawler job history explicitly associated with this campaign across its rounds.","parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}},{"description":"Opaque exclusive cursor from nextBefore. Omit for newest linked jobs.","in":"query","name":"before","schema":{"type":"string"}},{"description":"Optional originating revision; omitted or 0 includes all revisions.","in":"query","name":"revision","schema":{"minimum":0,"type":"integer"}},{"description":"Maximum jobs returned, default 100.","in":"query","name":"limit","schema":{"maximum":200,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"properties":{"hasMore":{"type":"boolean"},"jobs":{"items":{"$ref":"#/components/schemas/OverseerLinkedJob"},"type":"array"},"nextBefore":{"type":["string","null"]}},"required":["jobs","nextBefore","hasMore"],"type":"object"},"success":{"const":true}},"required":["success","data"],"type":"object"}}},"description":"Linked jobs, current queue status and stable backward cursor."},"400":{"description":"Malformed cursor, unsupported or duplicate query, or out-of-range limit/revision."},"401":{"description":"Authentication required."},"403":{"description":"Current user-backed staff/ADMIN operator required; preview denied."}},"security":[{"ApiKeyAuth":[]}],"summary":"Read linked campaign jobs","tags":["Internal Overseer"],"x-auth":"Current user-backed staff or ADMIN identity. Account credentials and machine keys belonging to eligible users are accepted; a bare service credential is not. Intelligence uses its signed session cookie; cookie PUT requests require a trusted origin and session-bound CSRF token.","x-curl-examples":[{"code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/jobs\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\"","title":"Read the newest linked jobs"}],"x-internal":true}},"/api/overseer/campaigns/{id}/brief":{"patch":{"tags":["Internal Overseer"],"summary":"Revise campaign context, criteria and active work limit","description":"Requires the reviewed campaign revision. Saves a visible immutable brief revision and starts another round, retaining staged proposals, exclusions and prior outcomes. Frozen membership, work types, field selection and geography stay fixed. Clearing end criteria selects the finite campaign default. Completed, cancelled or merging campaigns refuse revisions. Paused state remains independent.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaign"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Revise campaign context, criteria and active work limit","code":"curl -X PATCH \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/brief\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"Content-Type: application/json\" --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"expectedRevision":{"type":"integer","minimum":0},"name":{"type":"string","minLength":1},"context":{"type":"string","maxLength":8000},"endCriteria":{"type":"string","maxLength":8000},"activeWorkLimitSeconds":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}]}},"required":["expectedRevision","name","context","endCriteria","activeWorkLimitSeconds"]}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}}]}},"/api/overseer/campaigns/{id}/cancel":{"post":{"tags":["Internal Overseer"],"summary":"Cancel remaining campaign work","description":"Requires the reviewed revision. Saves a partial outcome report and revokes campaign execution while retaining staged proposals, exclusions, published history, and independently dispatched jobs. Cleanup retains execution occupancy until the native process has joined. Completed or merging campaigns refuse cancellation; a previously cancelled desired state is a no-op.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaign"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Cancel remaining campaign work","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/cancel\" -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" -H \"Content-Type: application/json\" --data @request.json"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"expectedRevision":{"type":"integer","minimum":0}},"required":["expectedRevision"]}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}}]}},"/api/overseer/campaigns/{id}/listings":{"get":{"tags":["Internal Overseer"],"summary":"Read unified campaign listings","description":"Returns one row per listing kind and canonical or provisional identity from the union of the checklist and proposal heads. Proposal summaries are compact; fetch exact proposal detail only when needed. Items contain only proposal summaries matching q, kind, outcome, operation and decision. Without operation, decision or hasProposals filters, matching checklist entries without proposals remain visible. total counts matching listings; proposalTotal counts matching heads; counts describes campaign-wide decisions and included operations. Search membership uses campaign-owned checklist names and saved proposal labels; current canonical names are display data. Stable kind:UUID cursor pagination has no offset ceiling.","x-internal":true,"x-auth":"Current user-backed staff or ADMIN operator. Cookie mutations require a trusted origin and session-bound CSRF token.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Campaign response.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/OverseerCampaignListingsPage"}},"required":["success","data"]}}}},"400":{"description":"Unsupported, repeated, malformed or out-of-range input."},"401":{"description":"Authentication required."},"403":{"description":"Staff or ADMIN operator and applicable browser policy required."},"404":{"description":"Campaign or campaign item not found."},"409":{"description":"Campaign version changed or request identity conflicts with an earlier submission."}},"x-curl-examples":[{"title":"Read unified campaign listings","code":"curl --get \"https://apis.schoolsoutapp.com/api/overseer/campaigns/$CAMPAIGN_ID/listings\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}],"parameters":[{"name":"id","in":"path","required":true,"description":"Campaign UUID.","schema":{"type":"string","format":"uuid"}},{"name":"q","in":"query","required":false,"description":"","schema":{"type":"string","maxLength":500}},{"name":"kind","in":"query","required":false,"description":"","schema":{"type":"string","enum":["organization","activity","session"]}},{"name":"operation","in":"query","required":false,"description":"","schema":{"type":"string","enum":["create","update","remove"]}},{"name":"decision","in":"query","required":false,"description":"","schema":{"type":"string","enum":["included","excluded","revision_requested"]}},{"name":"limit","in":"query","required":false,"description":"","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"outcome","in":"query","required":false,"schema":{"type":"string","enum":["pending","researching","unchanged","proposals","blocked","excluded"],"description":"Research outcome of the campaign checklist entry. Proposal-only candidates have no outcome."}},{"name":"hasProposals","in":"query","required":false,"schema":{"type":"boolean","description":"Require at least one proposal, independent of research outcome. Filter-based selections always resolve proposals."}},{"name":"after","in":"query","required":false,"description":"Last returned kind:UUID identity. Use nextAfter when hasMore is true.","schema":{"type":"string"}}]}},"/api/canonical/market-supply":{"get":{"summary":"Read Market supply for selected regions","description":"Named Market read. Tier masking, effective fields, and removal visibility apply before aggregation. Independent sections can fail with warnings without discarding successful sections. Extra and repeated query parameters are rejected. SQL statements are never accepted.","tags":["Internal canonical queries"],"x-internal":true,"x-auth":"Starter or above with the current catalog projection. Preview requires market_explorer.read and uses the disclosed fixed samples.","responses":{"200":{"description":"Success envelope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketSupplyResponse"}}}},"400":{"description":"Invalid, missing, extra, or repeated parameters."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential, plan, or preview does not permit this read."}},"x-curl-examples":[{"title":"Read Market supply for selected regions","code":"curl \"https://apis.schoolsoutapp.com/api/canonical/market-supply?regions=wa,washington&calibrationRegions=wa,washington\" -H \"x-api-key: $SCHOOLS_OUT_API_KEY\""}],"parameters":[{"name":"regions","in":"query","required":true,"description":"Comma-separated 1–100 non-empty region names/codes, each at most 80 characters. Case-insensitive literal matching; include desired code and name aliases.","schema":{"type":"string"}},{"name":"calibrationRegions","in":"query","required":true,"description":"Region names/codes for the comparison counts, with the same bounds as regions.","schema":{"type":"string"}}]}},"/api/canonical/market-supply/listed-counts":{"get":{"summary":"Count listed camps by city and region","description":"Named city/region counts for Market calibration; uses the same caller projection as Market supply. Extra and repeated query parameters are rejected. SQL statements are never accepted.","tags":["Internal canonical queries"],"x-internal":true,"x-auth":"Starter or above with the current catalog projection. Preview requires market_explorer.read and uses the disclosed fixed samples.","responses":{"200":{"description":"Success envelope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketListedCountsResponse"}}}},"400":{"description":"Invalid, missing, extra, or repeated parameters."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential, plan, or preview does not permit this read."}},"x-curl-examples":[{"title":"Count listed camps by city and region","code":"curl \"https://apis.schoolsoutapp.com/api/canonical/market-supply/listed-counts\" -H \"x-api-key: $SCHOOLS_OUT_API_KEY\""}]}},"/api/canonical/revenue-supply":{"get":{"summary":"Read the shared Revenue supply","description":"Named Revenue read for the shared server-side snapshot. Splits daily-priced sessions from non-daily groups; limits and truncation describe incomplete supply. Uses the server-controlled read deadline. Extra and repeated query parameters are rejected. SQL statements are never accepted.","tags":["Internal canonical queries"],"x-internal":true,"x-auth":"Undelegated service credential only; x-viewer-id, account credentials, machine keys, and preview sessions are refused.","responses":{"200":{"description":"Success envelope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevenueSupplyResponse"}}}},"400":{"description":"Invalid, missing, extra, or repeated parameters."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential, plan, or preview does not permit this read."}},"x-curl-examples":[{"title":"Read the shared Revenue supply","code":"curl \"https://apis.schoolsoutapp.com/api/canonical/revenue-supply\" -H \"x-api-key: $SCHOOLS_OUT_API_KEY\""}]}},"/api/canonical/session-calendar":{"get":{"summary":"Read sessions overlapping a calendar window","description":"Named Calendar read. Inclusive dates must be ordered and span at most ten years. Uses the effective catalog projection and excludes removed records. Extra and repeated query parameters are rejected. SQL statements are never accepted.","tags":["Internal canonical queries"],"x-internal":true,"x-auth":"Starter or above with the current catalog projection. Preview sessions are refused.","responses":{"200":{"description":"Success envelope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionCalendarResponse"}}}},"400":{"description":"Invalid, missing, extra, or repeated parameters."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential, plan, or preview does not permit this read."}},"x-curl-examples":[{"title":"Read sessions overlapping a calendar window","code":"curl \"https://apis.schoolsoutapp.com/api/canonical/session-calendar?startDate=2026-01-01&endDate=2026-12-31\" -H \"x-api-key: $SCHOOLS_OUT_API_KEY\""}],"parameters":[{"name":"startDate","in":"query","required":true,"description":"Inclusive window start.","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","required":true,"description":"Inclusive window end.","schema":{"type":"string","format":"date"}}]}},"/api/users/viewer/groups/{groupId}/plans":{"get":{"tags":["Internal family groups"],"x-internal":true,"summary":"Read a group's plans","description":"The group's plans, soonest first, with each member's answer.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["plans"],"properties":{"plans":{"type":"array","items":{"$ref":"#/components/schemas/FamilyGroupPlan"}}}}}}}}},"404":{"description":"Group not found, or you are not a member."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404.","x-curl-examples":[{"title":"Read a group's plans","code":"curl \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID/plans\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]},"post":{"tags":["Internal family groups"],"x-internal":true,"summary":"Propose a plan to a group","description":"Stores the plan and sends a message that shares it, as a send does: once per clientMessageId (a repeat returns the stored message, 200) and within the daily message limit. A group keeps at most 200 plans.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["clientMessageId","title","date"],"properties":{"clientMessageId":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,64}$","description":"Your idempotency key for the message that shares the plan; keep it when retrying."},"title":{"type":"string","minLength":1,"maxLength":120},"date":{"type":"string","format":"date"},"time":{"type":["string","null"],"pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","description":"Local time on date, HH:MM on a 24-hour clock."},"note":{"type":["string","null"],"maxLength":1000},"activityId":{"type":["string","null"],"format":"uuid","description":"A catalog activity the plan is for."}},"additionalProperties":false}}}},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["message"],"properties":{"message":{"$ref":"#/components/schemas/FamilyGroupMessage"}}}}}}}},"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["message"],"properties":{"message":{"$ref":"#/components/schemas/FamilyGroupMessage"}}}}}}}},"400":{"description":"Invalid clientMessageId, title, date, time, note or activityId."},"404":{"description":"Group not found, or you are not a member; or the activity is not in the catalog."},"409":{"description":"clientMessageId was used for a different message, or the group has the most plans allowed."},"429":{"description":"Daily message limit; retryAfterMs is how many milliseconds until another message is allowed. A session-backed request can also be refused by the family request limit (FamilyRequestLimit), with retryAfterMs and windowMs."},"401":{"description":"Current account required."}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404.","x-curl-examples":[{"title":"Propose a plan to a group","code":"curl -X POST \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID/plans\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"clientMessageId\":\"p-8f3a2c1d\",\"title\":\"Oaks Bottom walk\",\"date\":\"2026-11-07\",\"time\":\"10:00\"}'"}]}},"/api/users/viewer/groups/{groupId}/plans/{planId}":{"delete":{"tags":["Internal family groups"],"x-internal":true,"summary":"Delete a plan","description":"Deletes the plan with its answers. Whoever proposed it or the group's owner may; the message that shared it stays, with no plan.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"planId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["deleted"],"properties":{"deleted":{"const":true}}}}}}}},"403":{"description":"You neither proposed the plan nor own the group."},"404":{"description":"Group or plan not found, or you are not a member."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404.","x-curl-examples":[{"title":"Delete a plan","code":"curl -X DELETE \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID/plans/$PLAN_ID\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\""}]}},"/api/users/viewer/groups/{groupId}/plans/{planId}/answer":{"put":{"tags":["Internal family groups"],"x-internal":true,"summary":"Answer a plan","description":"Records whether you are going (yes, maybe or no); null takes your answer back.","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"planId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"enum":["yes","maybe","no",null]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["plan"],"properties":{"plan":{"$ref":"#/components/schemas/FamilyGroupPlan"}}}}}}}},"400":{"description":"answer is not yes, maybe, no or null."},"404":{"description":"Group or plan not found, or you are not a member."},"401":{"description":"Current account required."},"429":{"$ref":"#/components/responses/FamilyRequestLimit"}},"security":[{"AccountCredential":[]}],"x-auth":"Current account cookie or account credential only. Machine keys, investor previews and delegated service requests are rejected regardless of plan, billing team, staff or ADMIN level. Cookie mutations require CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Only the group's members see it; anyone else receives 404.","x-curl-examples":[{"title":"Answer a plan","code":"curl -X PUT \"https://apis.schoolsoutapp.com/api/users/viewer/groups/$GROUP_ID/plans/$PLAN_ID/answer\" \\\n  -H \"x-api-key: $SCHOOLS_OUT_ACCOUNT_CREDENTIAL\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"answer\":\"yes\"}'"}]}}},"security":[{"ApiKeyAuth":[]}],"servers":[{"description":"Production","url":"https://apis.schoolsoutapp.com"}],"tags":[{"name":"Data and insights","description":"Discover, filter, search, and read datasets through the supported public contract."},{"name":"Organization catalog","description":"Query the canonical organization catalog and its School’s Out scores."},{"name":"System","description":"Check whether the API is up."},{"name":"Internal canonical queries","description":"Schools Out account/Ask product contract. Machine keys are not accepted."},{"name":"Internal staff geocoding","description":"Staff/ADMIN operations for coverage and confirmed paid backfills. Not an external integration contract."},{"name":"Internal family profiles","description":"Private child profiles shared only with explicitly invited and accepted guardians. Machine keys and service impersonation are rejected."},{"name":"Internal family social","description":"Parents' saves, collections, follows, public profiles, friends and reports. Machine keys and service impersonation are rejected, except on the public collection and photo reads."},{"name":"Internal family groups","description":"Parents' group chats: groups of accepted friends, invitations, messages polled by seq, and Assistant answering in the thread. Machine keys and service impersonation are rejected."},{"name":"Internal family moderation","description":"Staff review of what parents report."},{"name":"Internal Explorer map","description":"Read-only geographic browsing for Intelligence. Catalog access and preview disclosure are enforced before spatial grouping."},{"name":"Internal Overseer","description":"Staff/ADMIN singleton directive and audit monitoring."},{"name":"Internal sign-in handoff","description":"Auth-website handoff into a registered School’s Out product: Intelligence or the mobile app. Not an external integration contract."},{"name":"Internal Gmail","description":"Private parent Gmail consent and connection status."},{"name":"Internal family account","description":"The signed-in parent's account: profile, devices, data export and connected Google accounts. Machine keys and service impersonation are rejected."},{"name":"Internal Google Calendar","description":"Google Calendar consent browser navigations: the native bootstrap and Google's return. The JSON connection routes are in Internal family account."},{"name":"Internal family bookings","description":"Parent-approved camp bookings: saved cards, the booking policy (service fee and group rate), approval with a spending limit, status polling, camp questions, applications and their outcomes, changes, cancellation and refunds, and the wallet with its group rate credit. The booking routes are registered only with BOOKINGS_ENABLED=true; the money routes (saved cards, wallet, top-ups, payments and receipts, gift cards, staff charge follow-ups) are always registered, and those that reach Stripe answer 503 without STRIPE_SECRET_KEY. Machine keys and service impersonation are rejected."},{"name":"Internal family calendar","description":"What a family plans: school search and each child's school, school calendars, plans made outside bookings, day marks, the composed calendar and Home. The viewer's own account only; machine keys and service impersonation are rejected."},{"name":"Internal school calendars","description":"Publishing school calendars from another process: a calendar source's days and term, for every school it serves. The service credential acting for an ADMIN or staff operator only."},{"name":"Internal Hallpass runs","description":"Staff/ADMIN Hallpass tasks of any kind outside a family booking: rehearse a checkout or evaluate Hallpass, then follow, answer and cancel it. Runs place the order by default, up to HALLPASS_STAFF_MAX_SPEND_CENTS; a rehearsal stops at the checkout total. Registered only when HALLPASS_URL is configured. Staff authenticate with their signed-in session or account credential, or with a named machine key they own, which may start, read, follow and cancel runs; answering a decision needs the signed-in person."},{"name":"Internal scoring methodology","description":"The organization rubric, formulas, and evidence policy used by the running scoring engine."},{"name":"Internal signed-out onboarding","description":"Read-only routes the parent app's first-time onboarding calls before the parent has an account. Off (404) until ONBOARDING_PUBLIC_KEYS is set; each call needs x-onboarding-key and counts against per-address limits. No account, no writes, nothing stored."}]}