Account, research, and catalog services. Use an account credential in x-api-key or an Intelligence browser session. Machine keys follow the permissions of their owning account. Hallpass, the task broker this API calls over Render’s private network, has its own reference for staff.
GET /api/activities
Lists canonical activities with Organization, city, region, category, age, skill, format, seasonality, sorting, and pagination filters. Activities have no enrollment price, currency, discount, or care-pricing fields; sessions own enrollment pricing and nullable freeform `discounts` text. The activity image_url compatibility read returns its selected active cover only. City and region match the activity's own recorded location, without parent-organization fallback. includeRows=false returns metadata and counts with an empty activities array. 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; filtered totals and returned rows remain live. canonical_url is the verified official primary source; relevant_urls is an array of supporting or unverified pages and does not establish an official site. Both fields require Team data access. 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 and SQL retain their existing contracts. Bookkeeping and provenance are available separately 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, SQL, and the legacy ageMonths filter retain month compatibility. Records and column metadata omit score. Fetch /api/activities/{id}/score for live detail or /api/activities/scores?ids=UUID,UUID&cached=true for persisted summaries; ordinary list/detail reads do not load scores. 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. 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. 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.
This endpoint requires an API key; fields and result windows are masked to the account plan
Returns canonical Activity category, skill, format, and seasonality facets. With ?field= it returns one facet scoped to the list's filters; field=age_group counts activities by the age bands (3-5, 6-8, 9-12, 13-17 years, and unknown) their age range overlaps, and field=updated_day counts them by the day of their latest update over the last 30 days. Optional snapshot=true with a single field serves incrementally maintained chart counts when no list filters or search are active; filtered requests and the default remain live. Pending source changes or due calendar updates fall back to live counts, so committed edits, deletion, and blacklisting remain visible before background refresh completes. Authentication and field access remain live.
Returns one tier-masked canonical Activity with its Organization and Session summaries. canonical_url is the verified official primary source; relevant_urls is an array of supporting or unverified pages and does not establish an official site. Both fields require Team data access. Activities have no enrollment price, currency, discount, or care-pricing fields. Session care-pricing quotes follow price access rules; schedule hours never grant access to nested care prices. Its image_url compatibility read returns the selected active cover only. 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 and SQL retain their existing contracts. Moved fields are configuration_id, 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, SQL, and the legacy ageMonths filter retain month compatibility. Records and column metadata omit score. Fetch /api/activities/{id}/score for live detail or /api/activities/scores?ids=UUID,UUID&cached=true for persisted summaries; ordinary list/detail reads do not load scores. 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.
Account cookie or header, or permitted machine key, from Starter up.
Returns one activity's paginated media gallery and selected cover ID with available image dimensions and permitted source attribution. limit defaults to 30 and caps at 100; offset defaults to 0 and caps at 1000000. Media belongs only to the requested activity; parent organization media is not inherited. Optional includeProvenance=false skips source attribution and its database read. Omitted or true preserves existing ADMIN source access; this flag never grants authority. Accepts one exact true or false value; malformed or repeated values return 400. Gallery-only pagination can use false; one image’s source disclosure can use true with limit=1 and that image’s offset.
Account cookie or header, or permitted machine key, from Starter up. Previews cannot read media. Source provenance is limited to non-preview ADMIN account viewers.
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. Moved fields are configuration_id, 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.
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.
Record how staff closed a wallet top-up or gift card purchase listed as unresolved, taking it off the follow-up list, for a staff or ADMIN person on their own account (not a machine key).
Direct current staff or ADMIN account, because the resolution names the person who made it. Service delegation, machine keys, and preview viewers are rejected. Cookie mutations require CSRF.
GET /api/admin/family-bookings/follow-ups
List bookings whose card refund or capture Stripe refused, and wallet top-ups and gift card purchases left unresolved, waiting on staff, for a staff or ADMIN person on their own account (not a machine key).
Direct current staff or ADMIN account. Service delegation, machine keys, and preview viewers are rejected.
Record how staff resolved a booking's refused card refund or capture, taking it off the follow-up list, for a staff or ADMIN person on their own account (not a machine key).
Direct current staff or ADMIN account, because the resolution names the person who made it. Service delegation, machine keys, and preview viewers are rejected. Cookie mutations require CSRF. Registered only when bookings are enabled.
POST /api/assistant
Ask Assistant a question with optional current-conversation history or a saved conversation, and optionally the viewer's approved family context, using the account's read-only canonical data access, and stream a short answer with suggested replies and grounded activity or session results. With webSearch true, a catalog miss or a confirmed gap in a returned listing can fall back to web search, whose pages are returned as web sources and reported as advertised. With documentIds, the family owner's own child documents are read with the question; a saved turn keeps only their ids, never the files. May propose budget, search-area or child-interest preferences or a remembered fact for the parent's separate exact-value review; Assistant never saves them. A parent asks as their own signed-in, email-verified account within a free daily question limit and a daily budget shared by all accounts, spending no Ask credits; staff and ADMIN accounts are not counted.
Staff or ADMIN account only during internal testing, decided from the stored account. Current account credential or signed Intelligence session with CSRF from a trusted or API_ASSISTANT_ORIGINS origin. Service delegation to a staff or ADMIN account is allowed only without includeFamilyContext or conversation. Machine keys and investor-preview sessions are refused.
curl -N -X POST https://apis.staging.schoolsoutapp.com/api/assistant -H "x-api-key: YOUR_ACCOUNT_CREDENTIAL" -H "Content-Type: application/json" -d '{"question":"Only afternoons","history":[{"question":"Art camps in Seattle for my 8-year-old?","answer":"Two options fit. Which dates work?"}]}'
GET /api/assistant/conversations
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.
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.
GET returns the conversation's turns in order. DELETE permanently removes the transcript and clears its references from saved provenance and remembered facts, which remain approved.
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.
GET /api/assistant/conversations/{conversationId}
GET returns the conversation's turns in order. DELETE permanently removes the transcript and clears its references from saved provenance and remembered facts, which remain approved.
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.
GET /api/assistant/suggestions
The questions Assistant offers before the parent types one, written from the viewer's family context (children, place, next uncovered day off) and, with subjectKind and subjectId, about the camp or activity page the parent is on; each carries the subject to send back with the question.
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.
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. Native flow only seals a short-lived authorization code and returns gmail=authorized&code=<one-use completion>; denial/failure includes no code. Only authenticated app completion may exchange and save a native grant. Every valid-state native outcome echoes challenge=<original public S256>; the app must match it to its pending attempt before completing or clearing its proof.
This endpoint is open to the public (Google redirect target); the one-time state and the starting browser's nonce cookie identify the parent
GET /api/auth/gmail-start
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.
Public, independent-auth browser navigation. A one-use 10-minute native bootstrap ticket identifies the starting account and app proof.
GET /api/auth/google-calendar-callback
Consumes the Google state and requires the starting browser nonce. Web flow exchanges the code, requires both Calendar scopes, stores the sealed grant, and returns calendar=connected, denied or failed. Native flow only seals a short-lived authorization code and returns calendar=authorized&code=<one-use completion>; denial or failure includes no code. Every valid-state native outcome echoes challenge=<original public S256>. A Google refusal (other than the parent declining) is recorded as failure on an existing connection.
This endpoint is open to the public (Google redirect target); the one-time state and the starting browser's nonce cookie identify the parent
GET /api/auth/google-calendar-start
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.
Public, independent-auth browser navigation. A one-use 10-minute native bootstrap ticket identifies the starting account and app proof.
GET /api/canonical/map
Returns a bounded GeoJSON map of all matching organization, activity, and session records. Requires bbox=west,south,east,north and zoom. World-anchored grid cells retain complete per-kind counts and coarsen to fit the feature budget without sampling. Accessible coordinates retain full stored precision on every plan; preview field masks apply before spatial selection. Coverage describes the full matching permitted population; viewport counts describe complete cells intersecting the viewport. Optional regionKind, regionCode, and regionDataset select an immutable source boundary. 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. Approximate and unlocated coverage remain separate. Optional includeCampPoints=true adds precise organization-only campPoints within the requested bounds, using the same masked cohort and snapshot. Features contain UUID, point geometry, and empty properties 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. Set includeCampScores=true to add properties.scoreBand from the persisted organization score. It 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 score-band reads. Workers own score calculation and refresh; map reads do not calculate scores. Approximate ZIP/state locations and child records are excluded; overflow returns HTTP 413 without truncation. 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. 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 requests with minimumScore or maximumScore filters and 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. collectionId requires a direct staff or ADMIN account (no service delegation, machine key, or preview) and selects staff-curated organizations and their coherently owned descendants in Explorer cohort reads. ageYears filters by whole completed years; ageMonths remains a legacy compatibility filter. Categories are independent on organizations, activities, and sessions and are never implicitly inherited.
Starter or above; preview requires explorer.browse_masked and retains its fixed disclosed sample.
Reads simplified display outlines from an imported geography snapshot. Supports state, metro, county, city, neighborhood, and ZIP kinds; provide bbox and zoom, q for name/code search, code for one boundary, or point=longitude,latitude for source polygons covering that point. Point is exclusive with bbox/code/q, accepts one finite longitude within ±180 and latitude within ±85, and includes source edges. Point lookup is independent of viewport, simplification, and overlay truncation; overlapping candidates retain stable code ordering and existing feature/byte budgets. Features carry immutable datasetId, kind, code, source, and vintage. City features distinguish incorporated places from Census-designated places. Availability and truncation are explicit; display geometry must never determine precise selected-region membership. 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. 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.
Starter or above; preview requires explorer.browse_masked.
Pages the complete membership of a map group using the same catalog and selected-region filters. Pass the returned groupId as group, optional limit, and the returned nextCursor as after. Members carry targetKind and id for detail reads. Co-located records remain individually accessible without invented location offsets. Reads the same durable map projection and masking rules as the map. Initial preparation returns HTTP 503 with code=map_cache_warming. 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. collectionId selects staff-curated organizations and their coherently owned descendants in Explorer cohort reads. ageYears filters by whole completed years; ageMonths remains a legacy compatibility filter. Categories are independent on organizations, activities, and sessions and are never implicitly inherited.
Starter or above; preview requires explorer.browse_masked and retains its fixed disclosed sample.
GET /api/canonical/map/stream
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.
Starter or above; preview requires explorer.browse_masked and retains its fixed disclosed sample.
Describes the viewer-visible canonical columns, including the activities view’s nullable parent-camp score, plus the cached organization_scores view available to account canonical queries. Preview sessions receive only policy-disclosed sample columns and no score relation, with preview.sample and resultLimit metadata. canonical_url is the verified official primary source; relevant_urls is an array of supporting or unverified pages and does not establish an official site. Both fields require Team data access.
This endpoint requires an API key; hidden columns are omitted for the account plan
Searches canonical Organizations, Activities, and Sessions. Ordinary account requests retain kind ordering and exact total; ordinary preview search returns 403 and requires mode=suggestions. Optional mode=suggestions returns one globally ranked bounded page (default 10, limit 1–20), without total: exact names, prefixes, then other name/place matches; ties use folded name, kind, and UUID. Suggestions match names, related names, city, and region; descriptions and notes remain in ordinary search only. q characters %, _, and ! match literally in suggestions. Each suggestion carries target_kind, id, name, parent_id, parent_name, city, region, verified, and nullable organization_id. Optional location contains the linked active nonblacklisted camp's saved latitude, longitude, and coordinate_precision, with full stored precision on every plan and finite map bounds ±85/±180; child coordinates and viewport centroids never supply it. Missing/unavailable camps have no camp ID or location. Preview matching and relationships are restricted to fixed disclosed per-kind samples and field masks.
Starter or above with current catalog access. Preview suggestions preserve the fixed disclosed sample.
Translate a plain-language catalog search into the current viewer's permitted structured filters, or request clarification.
Account or permitted machine credential required
GET /api/canonical/session-calendar
School’s Out canonical/session-calendar service.
Account or permitted machine credential required
GET /api/collections
GET lists shared staff-managed camp collections; archived=false is the default, archived=true selects archived collections, and archived=all includes both. POST {name} creates one. Active names are unique without regard to case. Responses include memberCount for retained memberships and availableMemberCount for camps that remain nondeleted and nonblacklisted. Collections contain organizations only; use collectionId on organization Explorer reads to browse members. Creator attribution becomes null if the creating account is deleted; collection membership is retained.
Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. Mutations from a signed Intelligence cookie require a trusted origin and session-bound CSRF token.
GET lists shared staff-managed camp collections; archived=false is the default, archived=true selects archived collections, and archived=all includes both. POST {name} creates one. Active names are unique without regard to case. Responses include memberCount for retained memberships and availableMemberCount for camps that remain nondeleted and nonblacklisted. Collections contain organizations only; use collectionId on organization Explorer reads to browse members. Creator attribution becomes null if the creating account is deleted; collection membership is retained.
Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. Mutations from a signed Intelligence cookie require a trusted origin and session-bound CSRF token.
GET reads a collection, including archived collections. PATCH {name} renames an active collection or PATCH {archived:true} archives it. Archived collections remain readable and preserve membership, but cannot be renamed, changed, or restored. Active names are unique without regard to case; the response includes retained and currently available camp counts. Creator attribution becomes null if the creating account is deleted; collection membership is retained.
Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. PATCH from a signed Intelligence cookie requires a trusted origin and session-bound CSRF token.
GET reads a collection, including archived collections. PATCH {name} renames an active collection or PATCH {archived:true} archives it. Archived collections remain readable and preserve membership, but cannot be renamed, changed, or restored. Active names are unique without regard to case; the response includes retained and currently available camp counts. Creator attribution becomes null if the creating account is deleted; collection membership is retained.
Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. PATCH from a signed Intelligence cookie requires a trusted origin and session-bound CSRF token.
Checks which of 1–100 requested organization UUIDs are available members of this shared collection. The organizationIds parameter is required, nonempty, and contains distinct UUIDs; input is UUID-normalized and deduplicated. The response returns {organizationIds, unavailableOrganizationIds} from one SQL snapshot, with no camp names. organizationIds contains available requested members; unavailableOrganizationIds contains requested IDs that are missing, deleted, or blacklisted without distinguishing why. Only absence from organizationIds for an available ID confirms non-membership; an unavailable ID omitted there cannot confirm removal. Active and archived collections are readable.
Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected.
PUT adds an available, nondeleted, nonblacklisted camp to an active collection. DELETE removes its membership. Membership is idempotent. Archived collections cannot be changed; archived memberships remain readable. A collection can contain organizations only.
Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. Mutations from a signed Intelligence cookie require a trusted origin and session-bound CSRF token.
curl -X PUT https://apis.staging.schoolsoutapp.com/api/collections/COLLECTION_UUID/organizations/CAMP_UUID -H "x-api-key: YOUR_ACCOUNT_KEY"
PUT /api/collections/{id}/organizations/{organizationId}
PUT adds an available, nondeleted, nonblacklisted camp to an active collection. DELETE removes its membership. Membership is idempotent. Archived collections cannot be changed; archived memberships remain readable. A collection can contain organizations only.
Direct current staff or ADMIN account session. Service delegation, machine keys, and preview viewers are rejected. Mutations from a signed Intelligence cookie require a trusted origin and session-bound CSRF token.
curl -X PUT https://apis.staging.schoolsoutapp.com/api/collections/COLLECTION_UUID/organizations/CAMP_UUID -H "x-api-key: YOUR_ACCOUNT_KEY"
POST /api/family-invitations/decline
School’s Out family-invitations/decline service.
Public endpoint
POST /api/family-invitations/preview
School’s Out family-invitations/preview service.
Public endpoint
POST /api/family-invitations/request-new
School’s Out family-invitations/request-new service.
Public endpoint
GET /api/family-reports
GET lists parents' reports in one status (open, reviewing, actioned, dismissed) for staff review.
Direct staff or ADMIN account only.
PATCH /api/family-reports/{reportId}
PATCH moves a report through review with an optional staff note; actioned and dismissed are final.
Direct staff or ADMIN account only. Cookie mutations require CSRF.
GET /api/featured-collections
Public read of Explore's curated shelves, each with its first items.
Public; an optional signed-in parent personalizes the response.
GET /api/feed/daily-notes
GET ?date= lists the day's daily notes for every area, staff- or model-written.
Current staff or ADMIN person with their own account credential or cookie; machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT {text} writes staff's daily note for a geography area and date, replacing any; DELETE removes it.
Current staff or ADMIN person with their own account credential or cookie; machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/feed/daily-notes/{datasetId}/{kind}/{code}/{date}
PUT {text} writes staff's daily note for a geography area and date, replacing any; DELETE removes it.
Current staff or ADMIN person with their own account credential or cookie; machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/global-settings
School’s Out global-settings service.
Public endpoint
GET /api/hallpass/batches
School’s Out hallpass/batches service.
Account or permitted machine credential required
POST /api/hallpass/batches
School’s Out hallpass/batches service.
Account or permitted machine credential required
POST /api/hallpass/batches/preview
School’s Out hallpass/batches/preview service.
Account or permitted machine credential required
GET /api/hallpass/batches/{batchId}
School’s Out hallpass/batches/{batchId} service.
Account or permitted machine credential required
POST /api/hallpass/batches/{batchId}/configure
School’s Out hallpass/batches/{batchId}/configure service.
Account or permitted machine credential required
GET /api/hallpass/nodes
School’s Out hallpass/nodes service.
Account or permitted machine credential required
GET /api/hallpass/pilgrim/deployments
School’s Out hallpass/pilgrim/deployments service.
Account or permitted machine credential required
POST /api/hallpass/pilgrim/deployments
School’s Out hallpass/pilgrim/deployments service.
Account or permitted machine credential required
POST /api/hallpass/pilgrim/deployments/{generation}/cancel
School’s Out hallpass/pilgrim/deployments/{generation}/cancel service.
Account or permitted machine credential required
POST /api/hallpass/pilgrim/deployments/{generation}/promote
School’s Out hallpass/pilgrim/deployments/{generation}/promote service.
Account or permitted machine credential required
GET /api/hallpass/pilgrim/management
School’s Out hallpass/pilgrim/management service.
Account or permitted machine credential required
POST /api/hallpass/pilgrim/nodes/{nodeId}/operations
School’s Out hallpass/pilgrim/nodes/{nodeId}/operations service.
Account or permitted machine credential required
POST /api/hallpass/pilgrim/publisher
School’s Out hallpass/pilgrim/publisher service.
Account or permitted machine credential required
POST /api/hallpass/plans
School’s Out hallpass/plans service.
Account or permitted machine credential required
GET /api/hallpass/runs
School’s Out hallpass/runs service.
Account or permitted machine credential required
POST /api/hallpass/runs
School’s Out hallpass/runs service.
Account or permitted machine credential required
GET /api/hallpass/runs/{runId}
School’s Out hallpass/runs/{runId} service.
Account or permitted machine credential required
GET /api/hallpass/runs/{runId}/activity
School’s Out hallpass/runs/{runId}/activity service.
Account or permitted machine credential required
POST /api/hallpass/runs/{runId}/cancel
School’s Out hallpass/runs/{runId}/cancel service.
Account or permitted machine credential required
POST /api/hallpass/runs/{runId}/decisions/{decisionId}
School’s Out hallpass/runs/{runId}/decisions/{decisionId} service.
Account or permitted machine credential required
POST /api/hallpass/runs/{runId}/decisions/{decisionId}/notices
School’s Out hallpass/runs/{runId}/decisions/{decisionId}/notices service.
Account or permitted machine credential required
POST /api/hallpass/runs/{runId}/reconcile
School’s Out hallpass/runs/{runId}/reconcile service.
Account or permitted machine credential required
GET /api/hallpass/runs/{runId}/screen
School’s Out hallpass/runs/{runId}/screen service.
Account or permitted machine credential required
GET /api/hallpass/runs/{runId}/trace
School’s Out hallpass/runs/{runId}/trace service.
Account or permitted machine credential required
GET /api/hallpass/settings
School’s Out hallpass/settings service.
Account or permitted machine credential required
GET /api/hallpass/video/{asset}
School’s Out hallpass/video/{asset} service.
Account or permitted machine credential required
GET /api/health
Minimal API availability check; operational database, credential, and write status is returned only to the service credential or ADMIN
The team's shared pool of Codex (ChatGPT) subscriptions that Trident workers lease, one per worker session. GET returns data.accounts (every subscription that is not removed): id, label, email, plan, status (pending_login, awaiting_approval, active, exhausted, login_failed, login_required, removing), device_url, user_code and code_expires_at while a worker waits for the device login, used_5h_pct, used_week_pct, resets_at (the limiting window's reset), resets_5h_at, resets_week_at, usage_ceiling_pct (1-100 or null), leased_by (instance_id, name) and leased_at, last_used_at, last_error (redacted, at most 300 characters), created_by, created_at and updated_at. POST with a label of 1-80 characters returns 201 with data.account in pending_login; a worker then runs the device login and shows its link and code. Responses never contain tokens, auth.json contents, or filesystem paths.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
POST /api/ingestion/codex-accounts
The team's shared pool of Codex (ChatGPT) subscriptions that Trident workers lease, one per worker session. GET returns data.accounts (every subscription that is not removed): id, label, email, plan, status (pending_login, awaiting_approval, active, exhausted, login_failed, login_required, removing), device_url, user_code and code_expires_at while a worker waits for the device login, used_5h_pct, used_week_pct, resets_at (the limiting window's reset), resets_5h_at, resets_week_at, usage_ceiling_pct (1-100 or null), leased_by (instance_id, name) and leased_at, last_used_at, last_error (redacted, at most 300 characters), created_by, created_at and updated_at. POST with a label of 1-80 characters returns 201 with data.account in pending_login; a worker then runs the device login and shows its link and code. Responses never contain tokens, auth.json contents, or filesystem paths.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
POST /api/ingestion/codex-accounts/{id}/remove
POST marks a shared Codex subscription removing and returns 202 with data.account; the worker holding it (or any worker when none holds it) stops using it, deletes the server's login directory, and marks it removed (removed subscriptions leave the list). Removal never signs the teammate out: no logout or token revocation runs. Removing one already being removed returns it unchanged; a removed or unknown subscription returns 404.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
POST /api/ingestion/codex-accounts/{id}/usage-ceiling
POST {"usage_ceiling_pct": 1-100 or null} sets or clears the subscription's usage ceiling and returns 200 with data.account. The ceiling applies to either window's total use, including the owner's own: at or above it workers stop leasing the subscription (running calls finish; the worker switches subscriptions or pauses on Codex), and it keeps its login. It returns after a fresh reading below the ceiling once its window resets, or at once when the ceiling is raised or cleared. Allowed in any status before removal. 400 for an invalid value; 404 for a removed or unknown subscription.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
GET /api/ingestion/crawler-campaigns
GET: 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. POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
POST /api/ingestion/crawler-campaigns
GET: 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. POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
POST /api/ingestion/crawler-campaigns/search-plan
POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-campaigns/{id}
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
POST /api/ingestion/crawler-campaigns/{id}/control
POST: 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 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-campaigns/{id}/discoveries
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-campaigns/{id}/homepages
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-campaigns/{id}/performance
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-campaigns/{id}/search-plan
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-campaigns/{id}/searches
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
POST /api/ingestion/crawler-campaigns/{id}/settings
POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-campaigns/{id}/settings-history
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-campaigns/{id}/urls
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-campaigns/{id}/urls/{url_id}
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-jobs
GET: 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. POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
POST /api/ingestion/crawler-jobs
GET: 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. POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-jobs/stream
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-jobs/{id}
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
POST /api/ingestion/crawler-jobs/{id}/cancel
POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-jobs/{id}/evaluations
GET: 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. POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
POST /api/ingestion/crawler-jobs/{id}/evaluations
GET: 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. POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-jobs/{id}/evaluations/{evaluationId}
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
POST /api/ingestion/crawler-jobs/{id}/evaluations/{evaluationId}/cancel
POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-jobs/{id}/export
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-jobs/{id}/results
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
GET /api/ingestion/crawler-jobs/{id}/stream
GET: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
POST /api/ingestion/crawler-jobs/{id}/trident-evaluations
POST: 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.
Direct HTTPS tailnet worker only: short-lived signed Bearer capability bound to this worker, browser origin and scope. No account cookies or keys. Central API returns 503.
POST /api/ingestion/crawler-worker/access
POST: 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.
Current staff or ADMIN account. Cookie access grants require CSRF; status reads use normal account authentication.
POST /api/ingestion/crawler-worker/control
School’s Out ingestion/crawler-worker/control service.
Account or permitted machine credential required
GET /api/ingestion/crawler-worker/events
School’s Out ingestion/crawler-worker/events service.
Account or permitted machine credential required
GET /api/ingestion/crawler-worker/status
GET: Reads only the latest worker count snapshot held in this API process. No detailed crawler data or database query is performed. Counts and timestamps are null until publication; jobs is empty. A stale snapshot remains visible after a worker disconnect and must be labeled with its observation time. Global totals cover all jobs. The worker bounds per-job summaries to 500, prioritizing campaigns; counts.omitted_job_summaries reports excluded summaries. Summaries contain only job identity, kind, status, revisions and numerical counts.
Current staff or ADMIN account. Cookie access grants require CSRF; status reads use normal account authentication.
GET /api/ingestion/excluded-sites
Excluded sites: websites staff exclude because they look like camp data but are not, or are too complex for now. GET pages them newest first (q, include=removed, limit, offset) with data.sites, total, limit and offset; each site has its creator's email and live_data, the canonical organisations on its host now. POST {url, reason_code, comment, source, source_id, revisit_at} excludes the host (lowercased, no scheme, port or path, leading www. stripped): 201 with data.site, created and cancelled_job_count (its queued and paused scraper jobs, booking inspections aside, failed with website_excluded), or 200 with the host's existing exclusion unchanged. Unknown reason codes return 400. Live data is never changed.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
Excluded sites: websites staff exclude because they look like camp data but are not, or are too complex for now. GET pages them newest first (q, include=removed, limit, offset) with data.sites, total, limit and offset; each site has its creator's email and live_data, the canonical organisations on its host now. POST {url, reason_code, comment, source, source_id, revisit_at} excludes the host (lowercased, no scheme, port or path, leading www. stripped): 201 with data.site, created and cancelled_job_count (its queued and paused scraper jobs, booking inspections aside, failed with website_excluded), or 200 with the host's existing exclusion unchanged. Unknown reason codes return 400. Live data is never changed.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
GET with repeated url values or a comma-separated hosts list (1-200 sites) returns data.sites: input, host, excluded and the active exclusion (site) or null, one per site sent, for tags on scraper pages. An entry that is not a website address or host comes back with an empty host, excluded false and site null; only no sites, more than 200 or another parameter returns 400.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
POST {comment} un-excludes a site and returns data.site with removed_at, removed_by and removal_comment; it stays listed with include=removed. Jobs the exclusion failed stay failed; retry them. 404 for an unknown ID.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
curl -X POST https://apis.staging.schoolsoutapp.com/api/ingestion/excluded-sites/EXCLUDED_SITE_UUID/remove -H "Content-Type: application/json" -H "x-api-key: SERVICE_KEY" -H "x-viewer-id: USER_UUID" -d '{"comment":"They run summer camps now."}'
GET /api/ingestion/geocoding/coverage
GET: 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. Coverage calculation timeouts return 503 with a retryable message; failed calculations have a retry cooldown.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
GET /api/ingestion/geocoding/jobs
POST: 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. GET: Paginated job history. Jobs retain their original scope, mode, quote, requester, outcomes, and timestamps.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
POST /api/ingestion/geocoding/jobs
POST: 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. GET: Paginated job history. Jobs retain their original scope, mode, quote, requester, outcomes, and timestamps.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
GET /api/ingestion/geocoding/jobs/{id}
GET: Returns current progress and retained quote/outcomes. Blocked or uncertain provider work is visible.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
POST /api/ingestion/geocoding/jobs/{id}/cancel
POST: 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.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
POST /api/ingestion/geocoding/preview
POST: 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.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
GET /api/ingestion/geocoding/regions
GET: Lists supported US states and, when state is supplied, recorded city/ZIP labels and their counts. City labels do not represent polygons.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
GET /api/ingestion/geocoding/status
GET: Fresh worker heartbeat, credential configuration, and reference availability without waiting for coverage counts. No catalog scan or provider call.
Current staff or ADMIN account; signed Intelligence cookie or account credential.
GET /api/ingestion/inbox/publications
Lists, one row per site and oldest waiting first, each site's newest website publication while it still awaits approval, with its batches, counts, changes against the site's last approved publication and the website approval policy's verdict. kind=all|refresh|new|auto_approve|has_flags filters the rows; counts ignore it
Approves and denies staged records of one website publication, named by all its batch IDs; records not named stay pending in the Inbox. Approved records move on to mapping; denied ones end rejected with the operator, time and reason. Denying an activity denies its sessions; an activity needs its organisation, and a session its activity, approved or already live
A staff or ADMIN user is required; cookie-authenticated mutations require the normal CSRF token, and service-credential mutations require x-viewer-id
GET lists paginated scraper jobs filtered by installation, website substring, exact normalized websiteOrigin or websiteTarget, kind, status (queued, running, paused, completed or failed), exact failure code (error, such as training_mismatch), or creation date. websiteTarget accepts one absolute credential-free HTTP(S) URL up to 8192 decoded bytes and matches exact normalized path/query scope across installations with root slash aliases; data.websiteTarget echoes the applied normalized target for capability detection. Count and page share a read snapshot ordered by descending creation time then job ID. websiteOrigin matches scheme and host across page paths and queries; scheme, subdomains, www, and nondefault ports stay distinct. All filters combine. POST queues an onboarding or refresh job for an installation, stamps the current job contract version, and requires an idempotency key. A repeated identical request returns its original job; a changed request with the same key conflicts.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
POST /api/ingestion/scraper-jobs
GET lists paginated scraper jobs filtered by installation, website substring, exact normalized websiteOrigin or websiteTarget, kind, status (queued, running, paused, completed or failed), exact failure code (error, such as training_mismatch), or creation date. websiteTarget accepts one absolute credential-free HTTP(S) URL up to 8192 decoded bytes and matches exact normalized path/query scope across installations with root slash aliases; data.websiteTarget echoes the applied normalized target for capability detection. Count and page share a read snapshot ordered by descending creation time then job ID. websiteOrigin matches scheme and host across page paths and queries; scheme, subdomains, www, and nondefault ports stay distinct. All filters combine. POST queues an onboarding or refresh job for an installation, stamps the current job contract version, and requires an idempotency key. A repeated identical request returns its original job; a changed request with the same key conflicts.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
GET /api/ingestion/scraper-jobs/failure-codes
School’s Out ingestion/scraper-jobs/failure-codes service.
Account or permitted machine credential required
GET /api/ingestion/scraper-jobs/website-status
GET returns data.websites for 1–50 repeated websiteUrl values (at most 8192 decoded bytes each), with nullable latest, completed and active job summaries for every unique normalized target, including no-history targets. Queue normalization preserves page/query scope; root slash/no-slash forms share status. Returned job.website stays exact for refresh artifact ownership. Active prefers running over paused or queued; other selections use newest creation time then job ID. Invalid or over-limit batches return 400 without truncation. This read never submits scraper work.
Current staff or ADMIN account; signed Intelligence cookie or account credential.
GET /api/ingestion/scraper-jobs/{id}
Reads one scraper job with attempt and event timelines, including each event's page, model call, failure detail, and counters when the worker reported them. Linked website-publication batches report their downstream pipeline state separately from scrape completion.
Current staff or ADMIN account; signed Intelligence cookie or account credential.
GET /api/ingestion/scraper-jobs/{id}/artifacts
Lists a scraper job's compact diagnostic artifacts (onboarding plan, page decisions, record selections, extractor turns, held records, schedule and classification decisions, adapter and final result) with sizes and omission reasons, without bodies.
Current staff or ADMIN account; signed Intelligence cookie or account credential.
GET /api/ingestion/scraper-jobs/{id}/artifacts/{artifactId}
Reads one diagnostic artifact of a scraper job with its JSON body. Omitted artifacts have no body. Artifacts never contain captured page bodies, the annotated DOM, or credentials.
Current staff or ADMIN account; signed Intelligence cookie or account credential.
POST /api/ingestion/scraper-jobs/{id}/priority
School’s Out ingestion/scraper-jobs/{id}/priority service.
Account or permitted machine credential required
POST /api/ingestion/scraper-jobs/{id}/retry
POST queues a failed job again as a new job (201, data.job) with the same installation, website, kind, settings and attempt limit, at the current job contract version. A failed job has at most one retry: a repeated request returns that earlier retry with 200, and nothing new is queued. A job that has not failed returns 409; an unknown job returns 404.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
GET /api/ingestion/scrapers
Lists scraper installations, their most recent process session, current work, connection status, and provider_pauses: each model provider the installation is paused on, with reason, paused_at, and the latest automatic health probe (probed_at, probe_error, probes). A paused installation claims no jobs. current_jobs lists every running job of the installation, oldest first (an empty array when idle); current_job is the first of them. codex_account is the shared Codex subscription (id, label, email) the installation's live session leases now, or null. build is the latest session's revision with the main_revision, behind_main and checked_at its server last reported (null until reported; behind_main is null when the server could not count it for the running build). update is the newest dashboard update request (id, action, status, target_revision, from_revision, to_revision, message, log_tail, requested_by, created_at, updated_at, finished_at), or null; a request running for over 15 minutes without a result is shown as failed with message no result. The job contract version is separate from build and database migration versions.
Current staff or ADMIN account; signed Intelligence cookie or account credential.
POST /api/ingestion/scrapers/{id}/rollback
POST asks the installation to put its previous build back and returns 202 with data.request (pending, target_revision null); it runs like an update. 409 when the installation already has an active request; 404 for an unknown installation.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
POST /api/ingestion/scrapers/{id}/update
POST asks the installation to update to the main revision its server last reported (build.main_revision) and returns 202 with data.request (pending). The installation's own worker drains its jobs (at most about 10 minutes, otherwise the request is refused as busy) and hands the request to the server's root-owned update script, which re-validates it, checks the new build's database migrations, installs it, and rolls back on failure. The request reports status (pending, draining, running, succeeded, rolled_back, failed, refused), from_revision, to_revision, and a redacted message and log_tail. 409 when the installation already has an active request or has not reported main's revision; 404 for an unknown installation.
Current staff or ADMIN account; signed Intelligence cookie or account credential. Cookie POST requests require CSRF.
GET /api/insights
The insights catalog: every research dataset your plan can read, with tier-filtered union capabilities at the top and column-aware capabilities on each dataset
This endpoint requires an API key (free tier and up); premium datasets are listed from the pro plan
Relevance-ranked, five-second search for up to five terms across visible name, provider, category, description, city, county, state, and country fields, with literal wildcard characters, exact filters, pagination, and actionable busy responses
This endpoint requires an API key (free tier and up)
Rows from one insights dataset with strict exact filters, up to five name searches in one request, sorting, pagination, and one-page CSV output; unknown or malformed parameters return 400
This endpoint requires an API key (free tier and up) — rows are masked to your plan, and free accounts see the first result
School’s Out internal/crawler-worker/controls service.
Public endpoint
POST /api/internal/crawler-worker/counts
POST: Consumes newline-delimited CrawlerWorkerCountSnapshot objects. Every line requires the active session and a positive, strictly increasing sequence; observed_at cannot be more than one minute ahead. Each accepted line immediately replaces the in-memory snapshot; a later invalid line does not roll back earlier lines. No counts are persisted in PostgreSQL. The stream is limited to 16 MiB and each line to 4 MiB.
Dedicated server-only crawler publisher Bearer credential; browser origins are rejected.
POST /api/internal/crawler-worker/ready-urls
POST: 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.
Dedicated server-only crawler publisher Bearer credential; browser origins are rejected.
POST /api/internal/crawler-worker/session
POST: Fences older publisher requests, clears the API process count cache and starts a new ready-URL sequence. Does not clear the persisted ready URL list. Reconnect after API restart or an expired publisher session. No browser access.
Dedicated server-only crawler publisher Bearer credential; browser origins are rejected.
POST /api/internal/pilgrim/management/operations/{operationId}/claim
School’s Out internal/pilgrim/management/operations/{operationId}/claim service.
Public endpoint
POST /api/internal/pilgrim/management/report
School’s Out internal/pilgrim/management/report service.
Public endpoint
POST /api/internal/pilgrim/nodes
School’s Out internal/pilgrim/nodes service.
Public endpoint
GET /api/internal/pilgrim/publisher
School’s Out internal/pilgrim/publisher service.
Public endpoint
POST /api/internal/pilgrim/publisher/deployments/{generation}/promote
School’s Out internal/pilgrim/publisher/deployments/{generation}/promote service.
Public endpoint
POST /api/internal/pilgrim/publisher/report
School’s Out internal/pilgrim/publisher/report service.
Public endpoint
POST /api/internal/pilgrim/releases
School’s Out internal/pilgrim/releases service.
Public endpoint
PUT /api/internal/pilgrim/releases/{releaseId}/source
School’s Out internal/pilgrim/releases/{releaseId}/source service.
Public endpoint
GET /api/internal/pilgrim/updater/bundle/{releaseId}
School’s Out internal/pilgrim/updater/bundle/{releaseId} service.
Public endpoint
POST /api/internal/pilgrim/updater/claim
School’s Out internal/pilgrim/updater/claim service.
Public endpoint
GET /api/internal/pilgrim/updater/deployment
School’s Out internal/pilgrim/updater/deployment service.
Public endpoint
POST /api/internal/pilgrim/updater/progress
School’s Out internal/pilgrim/updater/progress service.
Public endpoint
GET /api/media/logos/{file}
Read an organization logo rendition by content hash. Public, cross-origin readable and immutable.
Public endpoint
POST /api/onboarding/picks
Signed-out onboarding: suggests up to 8 activities with a session ahead near an area for the given children's ages and interests, with optional school days off. Read only; nothing is stored.
Public; requires x-onboarding-key (ONBOARDING_PUBLIC_KEYS). Off (404) until a key is configured. 60 requests a minute and 1,000 a day per client address.
GET /api/onboarding/places
Signed-out onboarding: finds areas by city, neighborhood, ZIP code or address, without storing a lookup; never returns a street address.
Public; requires x-onboarding-key (ONBOARDING_PUBLIC_KEYS). Off (404) until a key is configured. 60 requests a minute and 1,000 a day per client address.
GET /api/onboarding/schools
Signed-out onboarding: searches schools by name, nearness to a ZIP code or point, or both, with each school's district, point and calendar status.
Public; requires x-onboarding-key (ONBOARDING_PUBLIC_KEYS). Off (404) until a key is configured. 60 requests a minute and 1,000 a day per client address.
GET /api/organizations
Lists canonical organizations with search, location, independent organization category, whole-year age, grade, inclusive score range, sort, and pagination filters. minimumScore and maximumScore are inclusive, orderBy=score sorts using the cached SQL score view, total reflects score filtering, and pagination happens in SQL. Ordinary lists retain organizations awaiting score preparation. List reads do not load score summaries or calculate scores. Returns retryable 503 while score objects are being installed. Responds with tier, resultLimit, and lockedColumns; format=csv downloads a metered export (x-export-remaining) without score columns. 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. Filtered totals and returned rows remain live. canonical_url is the verified official primary source; relevant_urls is an array of supporting or unverified pages and does not establish an official site. Both fields require Team data access. ageYears filters by whole completed years; ageMonths remains a legacy compatibility filter; whole-year bounds adapt to inclusive month bounds as [yearMin*12,yearMax*12+11]. collectionId filters this direct-account staff-only organization cohort by shared camp collection; categories are read and filtered from the organization itself, without activity aggregation. 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. 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. 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 and SQL retain their existing contracts. 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, SQL, and the legacy ageMonths filter retain month compatibility. Records and column metadata omit score. Fetch /api/organizations/{id}/score for live detail or /api/organizations/scores?ids=UUID,UUID&cached=true for persisted summaries; ordinary list/detail reads do not load scores.
Any API key from the Starter plan up. Columns are masked to the plan (contacts, social links, canonical_url, relevant_urls, exact address, and postal code from Team); accessible coordinates retain full stored precision on every plan; Free gets 402. A parent's own account with a family profile reads at any tier, with the consumer-safe family fields added to its tier's.
Returns canonical organization city, region, and independent organization category facets without provenance or raw ingestion data. With ?field= it returns one facet scoped to the list's filters; field=updated_day additionally counts organizations by the day of their latest update over the last 30 days. Optional snapshot=true with a single field serves incrementally maintained chart counts when no list filters or search are active; filtered requests and the default remain live. Pending source changes or due calendar updates fall back to live counts, so committed edits, deletion, and blacklisting remain visible before background refresh completes. Authentication and field access remain live. ageYears filters by whole completed years; ageMonths remains a legacy compatibility filter; whole-year bounds adapt to inclusive month bounds as [yearMin*12,yearMax*12+11]. collectionId filters this direct-account staff-only organization cohort by shared camp collection; categories are read and filtered from the organization itself, without activity aggregation.
Any API key from the Starter plan up; Free gets 402
Read the running organization scoring rubric, formulas, and evidence rules.
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.
Read live or cached score summaries for a batch of canonical organizations.
Account cookie or header, or permitted machine key, from Starter up; Free gets 402. Free Ask scoring is internal to Go and cannot be enabled through a request header.
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. canonical_url is the verified official primary source; relevant_urls is an array of supporting or unverified pages and does not establish an official site. Both fields require Team data access. 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 and SQL retain their existing contracts. Moved fields are configuration_id, 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, SQL, and the legacy ageMonths filter retain month compatibility. Records and column metadata omit score. Fetch /api/organizations/{id}/score for live detail or /api/organizations/scores?ids=UUID,UUID&cached=true for persisted summaries; ordinary list/detail reads do not load scores. 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.
Account cookie or header, or permitted machine key, from Starter up. Active organization members receive their existing organization detail access. A parent's own account with a family profile reads at any tier, with the consumer-safe family fields added to its tier's.
GET /api/organizations/{id}/brand/assets/{assetId}
GET streams the asset bytes with its content type (add ?download=1 for an attachment); DELETE soft-deletes it
GET: Starter-or-up access, or an active account membership of this organization. Mutations: An ADMIN or staff actor, or an active account membership of this organization
Returns one organization's paginated media gallery, selected cover and logo IDs, image dimensions when available, and permitted source attribution. limit defaults to 30 and caps at 100; offset defaults to 0 and caps at 1000000. Uploaded images keep their existing protected brand-asset URLs. Media belongs only to the requested organization. Optional includeProvenance=false skips source attribution and its database read. Omitted or true preserves existing ADMIN source access; this flag never grants authority. Accepts one exact true or false value; malformed or repeated values return 400. Gallery-only pagination can use false; one image’s source disclosure can use true with limit=1 and that image’s offset.
Account cookie or header, or permitted machine key, from Starter up; active organization members receive the existing organization detail access. Previews cannot read media. Source provenance is limited to non-preview ADMIN account viewers. A parent's own account with a family profile reads at any tier, with the consumer-safe family fields added to its tier's.
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. Moved fields are configuration_id, 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.
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.
Read recorded camp, program, and session contributions, with reconstructed organization scores for ADMIN accounts.
Account or permitted machine credential required
GET /api/organizations/{id}/score
Read an organization's score, evidence, and peer standing.
Account cookie or header, or permitted machine key, from Starter up; Free gets 402. Evidence fields and the organization are masked to the plan; raw organization provenance is ADMIN-only.
GET returns the signed-in organization edit form with link:null. POST { organization?, clearFields?: string[], reason?, sourceURL?, removeRelevantUrls?: string[], brand?, assets?, submitter? } saves an attributed, reversible operator contribution using the current account. Omitted and blank/null organization fields are ignored; clearFields explicitly clears editable fields, including all relevant_urls; required fields such as name cannot be cleared. Use removeRelevantUrls to remove selected supporting links. A clear or replacement of a populated, different value requires reason; fills and same-value confirmations do not. sourceURL is an optional HTTP(S) field citation. Human edits outrank automated contributions, including human-approved Overseer proposals; source freshness applies within the same authority. organization.relevant_urls adds supporting links. removeRelevantUrls explicitly removes matching saved supporting links, including the last link, while preserving unspecified and concurrently added URLs; removal wins over a simultaneous addition of the same normalized URL. Both lists use HTTP(S) form normalization. canonical_url remains the separate official website. Duplicate additions and absent removals record no new ownership. A removal reversal restores only its originally untracked links or links with still-valid supporting contributions; later removals remain authoritative. Public update tokens do not accept removeRelevantUrls.
An ADMIN or staff actor, or an active account membership of this organization
GET /api/overseer
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 cancels its undelivered one-off messages; committed child jobs remain. 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.
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 and POST requests require a trusted origin and session-bound CSRF token.
PUT /api/overseer
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 cancels its undelivered one-off messages; committed child jobs remain. 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.
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 and POST requests require a trusted origin and session-bound CSRF token.
GET /api/overseer/campaigns
School’s Out overseer/campaigns service.
Account or permitted machine credential required
POST /api/overseer/campaigns
School’s Out overseer/campaigns service.
Account or permitted machine credential required
GET /api/overseer/campaigns/options
School’s Out overseer/campaigns/options service.
Account or permitted machine credential required
POST /api/overseer/campaigns/preview
School’s Out overseer/campaigns/preview service.
Account or permitted machine credential required
GET /api/overseer/campaigns/regions
School’s Out overseer/campaigns/regions service.
Account or permitted machine credential required
GET /api/overseer/campaigns/{id}
School’s Out overseer/campaigns/{id} service.
Account or permitted machine credential required
PATCH /api/overseer/campaigns/{id}/brief
School’s Out overseer/campaigns/{id}/brief service.
Account or permitted machine credential required
POST /api/overseer/campaigns/{id}/cancel
School’s Out overseer/campaigns/{id}/cancel service.
Account or permitted machine credential required
GET /api/overseer/campaigns/{id}/checklist
School’s Out overseer/campaigns/{id}/checklist service.
Account or permitted machine credential required
POST /api/overseer/campaigns/{id}/clear-memory
School’s Out overseer/campaigns/{id}/clear-memory service.
Account or permitted machine credential required
GET /api/overseer/campaigns/{id}/events
School’s Out overseer/campaigns/{id}/events service.
Account or permitted machine credential required
POST /api/overseer/campaigns/{id}/feedback
School’s Out overseer/campaigns/{id}/feedback service.
Account or permitted machine credential required
GET /api/overseer/campaigns/{id}/jobs
School’s Out overseer/campaigns/{id}/jobs service.
Account or permitted machine credential required
GET /api/overseer/campaigns/{id}/listings
School’s Out overseer/campaigns/{id}/listings service.
Account or permitted machine credential required
POST /api/overseer/campaigns/{id}/messages
School’s Out overseer/campaigns/{id}/messages service.
Account or permitted machine credential required
POST /api/overseer/campaigns/{id}/pause
School’s Out overseer/campaigns/{id}/pause service.
Account or permitted machine credential required
GET /api/overseer/campaigns/{id}/results
School’s Out overseer/campaigns/{id}/results service.
Account or permitted machine credential required
POST /api/overseer/campaigns/{id}/results/decision
School’s Out overseer/campaigns/{id}/results/decision service.
Account or permitted machine credential required
POST /api/overseer/campaigns/{id}/results/merge
School’s Out overseer/campaigns/{id}/results/merge service.
Account or permitted machine credential required
POST /api/overseer/campaigns/{id}/results/prepare
School’s Out overseer/campaigns/{id}/results/prepare service.
Account or permitted machine credential required
GET /api/overseer/campaigns/{id}/results/{proposalId}
School’s Out overseer/campaigns/{id}/results/{proposalId} service.
Account or permitted machine credential required
POST /api/overseer/clear-memory
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. Undelivered one-off requests are cancelled with message_cancelled events. The global pause state and its version are preserved.
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 and POST requests require a trusted origin and session-bound CSRF token.
GET /api/overseer/events
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. Internal subagent telemetry is excluded before pagination; its aggregate appears only in the current snapshot. 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. Staff one-off requests appear as user_message with requestId and text; message_delivered and message_cancelled events identify their requestId. Delivery acknowledges a successful receiving turn, not completed requested work. pause_changed records explicit global control changes with paused and pauseVersion; the instruction revision is unchanged.
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.
GET /api/overseer/evidence/{id}
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.
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.
GET /api/overseer/feedback
School’s Out overseer/feedback service.
Account or permitted machine credential required
POST /api/overseer/feedback/{kind}/{id}/decision
School’s Out overseer/feedback/{kind}/{id}/decision service.
Account or permitted machine credential required
GET /api/overseer/jobs
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.
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.
POST /api/overseer/messages
Queues a staff-authored request for the next worker turn without editing standing instructions or interrupting the current turn. Supply a canonical UUID requestId; retry identical requestId, expectedRevision and text 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 arriving during a turn remain queued for the next turn. Replacing the configuration or clearing memory cancels outstanding requests visibly. 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.
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 and POST requests require a trusted origin and session-bound CSRF token.
POST /api/overseer/pause
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.
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 and POST requests require a trusted origin and session-bound CSRF token.
GET /api/overseer/review-groups
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.
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.
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 either a 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.
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.
GET /api/overseer/reviews/{id}
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.
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.
POST /api/overseer/reviews/{id}/decision
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.
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.
GET /api/people/{userId}
GET reads another parent's profile, relation, public collections and, when shared, what they are going to.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/people/{userId}/photo
Public photo read; immutable when v names the current photo.
Public.
PUT /api/school-calendars/{sourceId}
School’s Out school-calendars/{sourceId} service.
Account or permitted machine credential required
GET /api/schools
GET searches schools by name (q), nearness (near=home|lat,lng) or both, with distance from home and calendar status.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/sessions
Lists canonical sessions with Activity, Organization, location, independent session category, registration status, date, sorting, and pagination filters. Sessions own registration_open_date, registration_close_date, `prices`, and nullable freeform `discounts` text; attendance dates and clocks come from schedule_configurations. Session pricing uses ordered `prices` entries of `{price, basis, currency, description?, before_care_pricing?, after_care_pricing?}`. Optional source-grounded descriptions, such as Members or Non-members, may describe any option, are trimmed and omitted when blank, and never establish a price alone or receive an inferred default label. `discounts` preserves source-stated terms as text, independently of the quotes and the early-bird flag. Each tuition value may be null when unknown, but an entry with all three tuition values null must include a care quote; `[]` means no listed price, and zero means explicitly free. includeRows=false returns metadata and counts with an empty sessions array. 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; filtered totals and returned rows remain live. canonical_url is the verified official primary source; relevant_urls is an array of supporting or unverified pages and does not establish an official site. Both fields require Team data access. 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 and SQL retain their existing contracts. Bookkeeping and provenance are available separately 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, SQL, and the legacy ageMonths filter retain month compatibility. Records and column metadata omit score. Fetch /api/sessions/{id}/score for live detail or /api/sessions/scores?ids=UUID,UUID&cached=true for persisted summaries; ordinary list/detail reads do not load scores. 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. 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. 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.
This endpoint requires an API key; fields and result windows are masked to the account plan
Returns canonical Session category, city, and registration-status facets. Categories are recorded on the session itself and are not inherited from its activity or organization. With ?field= it returns one facet scoped to the list's filters; field=start_week counts upcoming sessions by their starting week (Mondays, ascending), and field=updated_day counts them by the day of their latest update over the last 30 days. Optional snapshot=true with a single field serves incrementally maintained chart counts when no list filters or search are active; filtered requests and the default remain live. Pending source changes or due calendar updates fall back to live counts, so committed edits, deletion, and blacklisting remain visible before background refresh completes. Authentication and field access remain live.
Returns one tier-masked canonical Session and its parent Activity summary. canonical_url is the verified official primary source; relevant_urls is an array of supporting or unverified pages and does not establish an official site. Both fields require Team data access. Sessions own registration_open_date, registration_close_date, `prices`, and nullable freeform `discounts` text. Attendance dates and clocks come from schedule_configurations. Session pricing uses ordered `prices` entries of `{price, basis, currency, description?, before_care_pricing?, after_care_pricing?}`. Optional source-grounded descriptions, such as Members or Non-members, may describe any option, are trimmed and omitted when blank, and never establish a price alone or receive an inferred default label. `discounts` preserves source-stated terms as text, independently of the quotes and the early-bird flag. Each tuition value may be null when unknown, but an entry with all three tuition values null must include a care quote; `[]` means no listed price, and zero means explicitly free. Session care-pricing quotes follow price access rules; schedule hours never grant access to nested care prices. 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 and SQL retain their existing contracts. Moved fields are configuration_id, 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, SQL, and the legacy ageMonths filter retain month compatibility. Records and column metadata omit score. Fetch /api/sessions/{id}/score for live detail or /api/sessions/scores?ids=UUID,UUID&cached=true for persisted summaries; ordinary list/detail reads do not load scores. 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.
Account cookie or header, or permitted machine key, from Starter up.
Returns one session's paginated media gallery and selected cover ID with available image dimensions and permitted source attribution. limit defaults to 30 and caps at 100; offset defaults to 0 and caps at 1000000. Media belongs only to the requested session; parent activity and organization media are not inherited. Optional includeProvenance=false skips source attribution and its database read. Omitted or true preserves existing ADMIN source access; this flag never grants authority. Accepts one exact true or false value; malformed or repeated values return 400. Gallery-only pagination can use false; one image’s source disclosure can use true with limit=1 and that image’s offset.
Account cookie or header, or permitted machine key, from Starter up. Previews cannot read media. Source provenance is limited to non-preview ADMIN account viewers.
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. Moved fields are configuration_id, 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.
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.
GET returns what an approval for the session shares of the child's profile: {sessionId, alwaysShared, shareFields}.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Registered only when bookings are enabled.
GET /api/shared-collections/{collectionId}
Public read of a public collection by link, with an optional signed-in parent.
Public; an optional signed-in parent personalizes the response.
POST /api/staff/booking-profiles/authorizations
School’s Out staff/booking-profiles/authorizations service.
Account or permitted machine credential required
POST /api/staff/booking-profiles/authorizations/revoke
School’s Out staff/booking-profiles/authorizations/revoke service.
Account or permitted machine credential required
POST /api/staff/booking-profiles/preview
School’s Out staff/booking-profiles/preview service.
Account or permitted machine credential required
GET /api/staff/booking-profiles/readiness
School’s Out staff/booking-profiles/readiness service.
Account or permitted machine credential required
POST /api/staff/booking-profiles/search
School’s Out staff/booking-profiles/search service.
Account or permitted machine credential required
DELETE /api/tracking
GET lists follows with their alerts; POST follows a record (repeating it replaces alerts); DELETE unfollows.
Current account (cookie, account credential, or service credential with x-viewer-id). Cookie mutations require CSRF.
GET /api/tracking
GET lists follows with their alerts; POST follows a record (repeating it replaces alerts); DELETE unfollows.
Current account (cookie, account credential, or service credential with x-viewer-id). Cookie mutations require CSRF.
POST /api/tracking
GET lists follows with their alerts; POST follows a record (repeating it replaces alerts); DELETE unfollows.
Current account (cookie, account credential, or service credential with x-viewer-id). Cookie mutations require CSRF.
POST /api/users/authenticate-interstitial
Creates a 30-minute one-time access code. domain REDIRECT_SCHOOLS_OUT_INTELLIGENCE returns the Intelligence /auth-callback; REDIRECT_SCHOOLS_OUT_APP returns the app deep link (APP_AUTH_CALLBACK, default schoolsout://auth-callback) and requires challenge, a base64url SHA-256 of the app's verifier. Outside production the app may pass callback exp://<host>:<port>/--/auth-callback for Expo Go
This endpoint requires an API key for an account with a verified email (or ADMIN / staff)
Consumes an interstitial code once and returns the identity plus sessionKey for the product’s session. A code issued with a challenge also requires verifier; a missing or wrong verifier still consumes the code
This endpoint requires a one-time interstitial code
Email a one-shot sign-in link. Always answers success for a well-formed address — no account enumeration. intent (signin|signup) only changes the email copy. The redirect, domain, and optional challenge ride the link's handoff; domain REDIRECT_SCHOOLS_OUT_APP requires a challenge
Say whose account a reset link's code is for, masked ({email: "h•••@internet.dev"}), without spending the code. 401 for an unknown or expired code. Shares a per-client limit with reset-password-confirm
This endpoint requires a reset code
curl -X POST https://apis.staging.schoolsoutapp.com/api/users/reset-password-preview -H "Content-Type: application/json" -d '{"code": "RESET_CODE"}'
POST /api/users/sign-out
POST revokes the session the cookie's sid names; returns revoked:false when the credential names none.
School’s Out users/viewer/booking-authorizations service.
Account or permitted machine credential required
POST /api/users/viewer/booking-authorizations
School’s Out users/viewer/booking-authorizations service.
Account or permitted machine credential required
POST /api/users/viewer/booking-authorizations/revoke
School’s Out users/viewer/booking-authorizations/revoke service.
Account or permitted machine credential required
GET /api/users/viewer/booking-policy
Reads the booking money policy: {serviceFee:{perChild}, groupRate:{perFamily, families}|null}, the values every new booking is charged and credited.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Registered only when bookings are enabled.
POST /api/users/viewer/booking-quote
School’s Out users/viewer/booking-quote service.
Account or permitted machine credential required
GET /api/users/viewer/bookings
GET lists the viewer's bookings newest first, keyset-paged (limit, cursor → nextCursor), filtered by childId, from/to (session range overlap) and phase. POST approves a booking: {idempotencyKey, childProfileId, childProfileVersion, sessionId, maxSpend {amountCents, currency}, allowWaitlist, autoAcceptDecisions, shareFields, answers, approveBooking:true, useCreditsCents?, paymentMethod?, reuseAllowed?: true, profileFields?}. reuseAllowed records the parent's reuse consent on the child and profileFields saves new child details to it first, through the children writer. The camp's guardian is the adult booking (the owner or a co-parent): their account's name, email and contact phone, never the child's profile. The owner's saved card is held for maxSpend (and credits held from the wallet) before Hallpass receives the task; a replayed idempotencyKey returns the same booking with 200.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Registered only when bookings are enabled.
POST /api/users/viewer/bookings
GET lists the viewer's bookings newest first, keyset-paged (limit, cursor → nextCursor), filtered by childId, from/to (session range overlap) and phase. POST approves a booking: {idempotencyKey, childProfileId, childProfileVersion, sessionId, maxSpend {amountCents, currency}, allowWaitlist, autoAcceptDecisions, shareFields, answers, approveBooking:true, useCreditsCents?, paymentMethod?, reuseAllowed?: true, profileFields?}. reuseAllowed records the parent's reuse consent on the child and profileFields saves new child details to it first, through the children writer. The camp's guardian is the adult booking (the owner or a co-parent): their account's name, email and contact phone, never the child's profile. The owner's saved card is held for maxSpend (and credits held from the wallet) before Hallpass receives the task; a replayed idempotencyKey returns the same booking with 200.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Registered only when bookings are enabled.
GET /api/users/viewer/bookings/{bookingId}
Returns one of the viewer's bookings with its status, payment status, pending decision, verified receipt, failure, activity, cancellation terms and event history. pollAfterMs suggests when to read again and is null once nothing changes without the parent.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Registered only when bookings are enabled.
POST /api/users/viewer/bookings/{bookingId}/cancel
POST {refundMethod?, reason?} cancels before checkout pays; for a booking the camp cancelled it records the refund choice (credits now, or card via Stripe). Each booking's cancellation field says beforehand what this route does with it. 409 when final, paying, already refunded or nothing was paid.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Registered only when bookings are enabled.
POST /api/users/viewer/bookings/{bookingId}/change
POST {idempotencyKey, sessionId, maxSpend?, refundMethod?} starts a linked booking for another open session of the same activity; the original is cancelled and refunded only once the new one is booked. 202 {booking, replaces, warnings}.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Registered only when bookings are enabled.
POST /api/users/viewer/bookings/{bookingId}/decisions/{decisionId}
Answers the pending decision with {outcome: accept, optionId} or {outcome: decline}. A decision that is no longer pending returns 409.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Registered only when bookings are enabled.
POST /api/users/viewer/bookings/{bookingId}/outcome
POST {outcome: registered|not_registered|not_yet|provider_task_done} records the parent's answer about an application or a task on the camp's site.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Registered only when bookings are enabled.
GET /api/users/viewer/calendar
GET ?from=&to=&childId=&detail=day|summary composes school days, plans, marks, alerts, friends' open days and (detail=day) Google events per day and child: {days, months}.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/care-requirements
GET lists the adult's dated care requirements with currently permitted children. POST saves dates or a range, weekdays, local hours, time zone, recurrence, exceptions, flexibility, coverage and optional budget/travel overrides for one to ten permitted children.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
POST /api/users/viewer/care-requirements
GET lists the adult's dated care requirements with currently permitted children. POST saves dates or a range, weekdays, local hours, time zone, recurrence, exceptions, flexibility, coverage and optional budget/travel overrides for one to ten permitted children.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
PATCH {version, changes} corrects named parts and revalidates the whole requirement. DELETE {version} removes it.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
POST /api/users/viewer/care-requirements/{requirementId}/confirmations
POST {version, keys} reconfirms named unchanged parts of the requirement.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
POST /api/users/viewer/child-invitations/accept
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.
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 /api/users/viewer/child-invitations/preview
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.
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.
GET /api/users/viewer/children
GET lists private child profiles with role owner or guardian. POST creates one of up to 20 owned profiles from approved values: a name is optional (a child may be named later), and registration reuse (default false) needs both names. Fields contain canonical registration strings; preferences contain up to 20 interests with an unknown/none/listed status and notes; planning contains a label, an age with its as-of date (instead of a birthdate), the grade's school year and participation requirements (required or preference, by category, without diagnoses). Current-value provenance records source (manual, or assistant with the viewer's own conversation turn), actor, approval time and record version. No previous values are retained. GET ?include=school adds each child's school (E-55's shape, null when unanswered), read for all children in one query. Each child carries photoUrl (null without a photo); fields may include profile.location (a place label) and profile.color (an accent name).
Current account cookie or account credential only; machine keys and delegated service requests are rejected. Lists owned profiles and profiles with an accepted, active guardian membership.
POST /api/users/viewer/children
GET lists private child profiles with role owner or guardian. POST creates one of up to 20 owned profiles from approved values: a name is optional (a child may be named later), and registration reuse (default false) needs both names. Fields contain canonical registration strings; preferences contain up to 20 interests with an unknown/none/listed status and notes; planning contains a label, an age with its as-of date (instead of a birthdate), the grade's school year and participation requirements (required or preference, by category, without diagnoses). Current-value provenance records source (manual, or assistant with the viewer's own conversation turn), actor, approval time and record version. No previous values are retained. GET ?include=school adds each child's school (E-55's shape, null when unanswered), read for all children in one query. Each child carries photoUrl (null without a photo); fields may include profile.location (a place label) and profile.color (an accent name).
Current account cookie or account credential only; machine keys and delegated service requests are rejected. Lists owned profiles and profiles with an accepted, active guardian membership.
DELETE /api/users/viewer/children/{childId}
PUT fully replaces fields and preferences with the current version; omitted or blank optional fields are removed, while omitting planning keeps it. PATCH {version, changes} corrects only named values; null removes one and reuseAllowed is not correctable there. Only the owner may change reuseAllowed or DELETE. DELETE {version} permanently removes the profile, guardian memberships, invitations, care requirement links and child-linked remembered facts; registration snapshots and receipts remain. All writes lock the actor account and profile before rechecking current membership. Stale versions return 409.
Current profile owner or accepted active guardian for PUT; DELETE is owner-only. Mutations require cookie CSRF. Unrelated accounts receive 404 regardless of plan or staff role.
PATCH /api/users/viewer/children/{childId}
PUT fully replaces fields and preferences with the current version; omitted or blank optional fields are removed, while omitting planning keeps it. PATCH {version, changes} corrects only named values; null removes one and reuseAllowed is not correctable there. Only the owner may change reuseAllowed or DELETE. DELETE {version} permanently removes the profile, guardian memberships, invitations, care requirement links and child-linked remembered facts; registration snapshots and receipts remain. All writes lock the actor account and profile before rechecking current membership. Stale versions return 409.
Current profile owner or accepted active guardian for PUT; DELETE is owner-only. Mutations require cookie CSRF. Unrelated accounts receive 404 regardless of plan or staff role.
PUT /api/users/viewer/children/{childId}
PUT fully replaces fields and preferences with the current version; omitted or blank optional fields are removed, while omitting planning keeps it. PATCH {version, changes} corrects only named values; null removes one and reuseAllowed is not correctable there. Only the owner may change reuseAllowed or DELETE. DELETE {version} permanently removes the profile, guardian memberships, invitations, care requirement links and child-linked remembered facts; registration snapshots and receipts remain. All writes lock the actor account and profile before rechecking current membership. Stale versions return 409.
Current profile owner or accepted active guardian for PUT; DELETE is owner-only. Mutations require cookie CSRF. Unrelated accounts receive 404 regardless of plan or staff role.
POST /api/users/viewer/children/{childId}/confirmations
POST {version, keys} records a fresh approval of named unchanged values and advances the version. A key without a saved value returns 409.
Current profile owner or accepted active guardian; account cookie or account credential only. Cookie requests require CSRF.
GET /api/users/viewer/children/{childId}/documents
School’s Out users/viewer/children/{childId}/documents service.
Account or permitted machine credential required
POST /api/users/viewer/children/{childId}/documents
School’s Out users/viewer/children/{childId}/documents service.
School’s Out users/viewer/children/{childId}/documents/{documentId} service.
Account or permitted machine credential required
GET /api/users/viewer/children/{childId}/documents/{documentId}
School’s Out users/viewer/children/{childId}/documents/{documentId} service.
Account or permitted machine credential required
GET /api/users/viewer/children/{childId}/guardians
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.
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.
Current profile owner only; machine, service and delegated-service credentials are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/children/{childId}/invitations
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. Optional redirect: an absolute URL on an allow-listed origin (AUTH_ORIGIN, PARENT_SITE_ORIGIN, API_ASSISTANT_ORIGINS) that the link opens with the token in its fragment; otherwise Intelligence's /family/invitations#<token>.
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.
Current profile owner only; machine, service and delegated-service credentials are rejected. Cookie mutations require CSRF.
DELETE /api/users/viewer/children/{childId}/photo
PUT uploads a JPEG or PNG photo through the parent photo's pipeline, stored as a 512×512 JPEG; GET serves it privately (never to a shared cache); DELETE removes it.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. The child's owner and accepted guardians only; others get 404.
GET /api/users/viewer/children/{childId}/photo
PUT uploads a JPEG or PNG photo through the parent photo's pipeline, stored as a 512×512 JPEG; GET serves it privately (never to a shared cache); DELETE removes it.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. The child's owner and accepted guardians only; others get 404.
PUT /api/users/viewer/children/{childId}/photo
PUT uploads a JPEG or PNG photo through the parent photo's pipeline, stored as a 512×512 JPEG; GET serves it privately (never to a shared cache); DELETE removes it.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. The child's owner and accepted guardians only; others get 404.
GET reads a child's school; PUT sets {schoolId} or {other, name?}; DELETE forgets it.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Owners and guardians read; only the owner writes.
GET /api/users/viewer/children/{childId}/school
GET reads a child's school; PUT sets {schoolId} or {other, name?}; DELETE forgets it.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Owners and guardians read; only the owner writes.
PUT /api/users/viewer/children/{childId}/school
GET reads a child's school; PUT sets {schoolId} or {other, name?}; DELETE forgets it.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF. Owners and guardians read; only the owner writes.
GET /api/users/viewer/collections
GET lists the parent's collections and the public ones they follow; POST creates one (1–60 characters, not "Saved", unique per parent).
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/collections
GET lists the parent's collections and the public ones they follow; POST creates one (1–60 characters, not "Saved", unique per parent).
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT adds an item (saving it); DELETE removes it, leaving it saved. Owner only.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/collections/{collectionId}/items/{itemKey}
PUT adds an item (saving it); DELETE removes it, leaving it saved. Owner only.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/day-marks/{date}
PUT merges the family's marks for a day ({open?, children?: {childId: {need?, googleEventCovers?, conflict?, hiddenIdeas?}}}; null clears) and answers {date, marks}.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/email-change
POST {email, redirect?} emails a 30-minute link to the new address and answers {pending: {email, expiresAt}}; the profile's pendingEmail shows it and the account keeps its email until the link is confirmed. Asking again replaces the pending change. 409 when another account holds the address.
A signed-in device only: the account credential with its live session; a bare account key, machine keys and delegated service requests are rejected.
POST {code} moves the account to the confirmed address (verified), rotates its key, signs every device out and starts this device's session in one transaction; answers {user, key, session_id} like a sign-in. 410 for an expired, replaced or foreign code; 409 when another account took the address meanwhile.
POST queues a family data export (202); the worker emails a single-use download link. 429 while one is unfinished or after a recent request.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys.
GET /api/users/viewer/exports/{exportId}
GET returns the viewer's export status.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys.
GET /api/users/viewer/family-context
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), 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, canonical SQL and research Ask never read these records. Booked-session context includes `prices`, scheduleConfigurations, and independently recorded care quotes. Effective date bounds and uniform clocks summarize explicit configurations; sparse dates never imply daily attendance.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
POST /api/users/viewer/family-invitations/accept
School’s Out users/viewer/family-invitations/accept service.
Account or permitted machine credential required
POST /api/users/viewer/family-invitations/decline
School’s Out users/viewer/family-invitations/decline service.
Account or permitted machine credential required
GET /api/users/viewer/family-memory
GET lists explicitly approved remembered facts. POST {approve:true, statement, category, childId?, expiresOn?, source?} remembers the exact approved statement; conversations never save facts automatically. Assistant source is {type:assistant, conversationId, turnId} and must name the viewer’s completed turn. Repeating that source and the same exact values returns the existing fact without changing its approval; deleted sources are refused.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
POST /api/users/viewer/family-memory
GET lists explicitly approved remembered facts. POST {approve:true, statement, category, childId?, expiresOn?, source?} remembers the exact approved statement; conversations never save facts automatically. Assistant source is {type:assistant, conversationId, turnId} and must name the viewer’s completed turn. Repeating that source and the same exact values returns the existing fact without changing its approval; deleted sources are refused.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
DELETE /api/users/viewer/family-memory/{factId}
PATCH {version, changes} corrects a fact. DELETE {version} forgets it.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
PATCH /api/users/viewer/family-memory/{factId}
PATCH {version, changes} corrects a fact. DELETE {version} forgets it.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
POST /api/users/viewer/family-memory/{factId}/confirmations
POST {version} renews approval of the unchanged statement.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
POST /api/users/viewer/family/invitations
School’s Out users/viewer/family/invitations service.
School’s Out users/viewer/family/invitations/{invitationId} service.
Account or permitted machine credential required
POST /api/users/viewer/family/invitations/{invitationId}/link
School’s Out users/viewer/family/invitations/{invitationId}/link service.
Account or permitted machine credential required
POST /api/users/viewer/family/invitations/{invitationId}/resend
School’s Out users/viewer/family/invitations/{invitationId}/resend service.
Account or permitted machine credential required
POST /api/users/viewer/family/leave
School’s Out users/viewer/family/leave service.
Account or permitted machine credential required
GET /api/users/viewer/family/members
School’s Out users/viewer/family/members service.
Account or permitted machine credential required
DELETE /api/users/viewer/family/members/{userId}
School’s Out users/viewer/family/members/{userId} service.
Account or permitted machine credential required
PATCH /api/users/viewer/family/members/{userId}
School’s Out users/viewer/family/members/{userId} service.
Account or permitted machine credential required
GET /api/users/viewer/feed
GET returns the family feed's current edition, published on first read and the only one a family can read, and the day's note for the household's area.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/feed/camp-views
POST {targetKind, targetId, dwellSeconds} records time the parent spent on a camp, activity or session page.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/feed/instructions
GET reads the signed-in adult's own feed instructions (null while on the default) and the feed's default wording for them; PUT {instructions: string | null} saves them or, with null, returns the adult to the default. Every role reads and writes only their own.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/feed/instructions
GET reads the signed-in adult's own feed instructions (null while on the default) and the feed's default wording for them; PUT {instructions: string | null} saves them or, with null, returns the adult to the default. Every role reads and writes only their own.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/feed/note
School’s Out users/viewer/feed/note service.
Account or permitted machine credential required
GET /api/users/viewer/feed/notes/saved
School’s Out users/viewer/feed/notes/saved service.
Account or permitted machine credential required
DELETE /api/users/viewer/feed/notes/{date}/saved
School’s Out users/viewer/feed/notes/{date}/saved service.
Account or permitted machine credential required
PUT /api/users/viewer/feed/notes/{date}/saved
School’s Out users/viewer/feed/notes/{date}/saved service.
Account or permitted machine credential required
PUT /api/users/viewer/feed/notes/{date}/seen
PUT marks the daily note for date seen and returns the first time it was; every edition shows a note, so 404 only when the family has no edition that day.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/feed/posts/{postId}/signal
PUT {signal: like | dislike | dont_show_again | null} sets or clears the post's one signal; idempotent for the viewer's own post, 404 only for another family's.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT follows a visible public collection; DELETE unfollows.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/followed-collections/{collectionId}
PUT follows a visible public collection; DELETE unfollows.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/friends
GET lists friends, incoming or requested requests, or blocked parents.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/friends-going
GET lists sharing friends signed up for up to 100 activities or sessions.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/friends/{userId}
PUT requests, accepts, declines, cancels, removes, blocks or unblocks.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/gift-cards
GET lists gift cards the viewer bought (last four code characters only, emailStatus). POST {idempotencyKey, amountCents, recipientName?, recipientEmail?, message?, paymentMethod?} charges a saved card and returns {giftCard, code}; the code is stored sealed (a replay answers with the same code) and, with recipientEmail, emailed to the recipient.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/gift-cards
GET lists gift cards the viewer bought (last four code characters only, emailStatus). POST {idempotencyKey, amountCents, recipientName?, recipientEmail?, message?, paymentMethod?} charges a saved card and returns {giftCard, code}; the code is stored sealed (a replay answers with the same code) and, with recipientEmail, emailed to the recipient.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/gift-cards/{giftCardId}/code
GET shows the purchaser a gift card's code again, active or redeemed: {giftCard, code}, never cached. Another account's card is 404.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
DELETE /api/users/viewer/gmail
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.
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.
GET /api/users/viewer/gmail
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.
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.
POST /api/users/viewer/gmail/authorization
Starts Gmail access and returns {url}. Web requests use an optional parent-site redirect and receive the browser nonce cookie. Native requests send {redirect, challenge}, an exact schoolsout://gmail-callback (or exp://host:port/--/gmail-callback outside production) and unpadded base64url SHA-256 app proof. Their URL is a one-use 10-minute API browser bootstrap that sets its own cookie before Google consent. No account key or verifier belongs in that URL.
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.
POST /api/users/viewer/gmail/complete
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.
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.
POST /api/users/viewer/gmail/send
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.
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.
DELETE /api/users/viewer/google-calendar
Returns the viewer's own Google Calendar grant, without its token. Disconnected accounts get connected: false, nulls and the default sync choices. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's. PATCH {sync?: {confirmed?, waitlisted?}, calendars?: string[]} changes which of the family's bookings the worker writes to School's Out's own calendar in the viewer's Google account, and which of the viewer's calendars are read. Unknown properties, non-boolean sync values and calendar IDs that are not in calendars return 400; 404 when not connected. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's. Deletes School's Out's own calendar from the viewer's Google account (and so every booking event School's Out wrote there; best effort, it never blocks the disconnect), revokes the refresh token at Google, then deletes the grant and its cached events and returns {connected: false, revoked}. A token Google already considers invalid counts as revoked. Any other revocation failure returns 502 and keeps the grant; a grant reconnected during the request returns 409. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's.
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.
GET /api/users/viewer/google-calendar
Returns the viewer's own Google Calendar grant, without its token. Disconnected accounts get connected: false, nulls and the default sync choices. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's. PATCH {sync?: {confirmed?, waitlisted?}, calendars?: string[]} changes which of the family's bookings the worker writes to School's Out's own calendar in the viewer's Google account, and which of the viewer's calendars are read. Unknown properties, non-boolean sync values and calendar IDs that are not in calendars return 400; 404 when not connected. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's. Deletes School's Out's own calendar from the viewer's Google account (and so every booking event School's Out wrote there; best effort, it never blocks the disconnect), revokes the refresh token at Google, then deletes the grant and its cached events and returns {connected: false, revoked}. A token Google already considers invalid counts as revoked. Any other revocation failure returns 502 and keeps the grant; a grant reconnected during the request returns 409. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's.
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.
PATCH /api/users/viewer/google-calendar
Returns the viewer's own Google Calendar grant, without its token. Disconnected accounts get connected: false, nulls and the default sync choices. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's. PATCH {sync?: {confirmed?, waitlisted?}, calendars?: string[]} changes which of the family's bookings the worker writes to School's Out's own calendar in the viewer's Google account, and which of the viewer's calendars are read. Unknown properties, non-boolean sync values and calendar IDs that are not in calendars return 400; 404 when not connected. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's. Deletes School's Out's own calendar from the viewer's Google account (and so every booking event School's Out wrote there; best effort, it never blocks the disconnect), revokes the refresh token at Google, then deletes the grant and its cached events and returns {connected: false, revoked}. A token Google already considers invalid counts as revoked. Any other revocation failure returns 502 and keeps the grant; a grant reconnected during the request returns 409. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's.
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.
POST /api/users/viewer/google-calendar/authorization
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. Personal: every adult of a family (owner, co-parent or caregiver) reaches only their own connection; nobody manages another adult's.
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.
POST /api/users/viewer/google-calendar/complete
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, saves the grant and returns the connection. 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.
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.
GET /api/users/viewer/google-calendar/events
Returns the family's merged Google Calendar: the cached events, overlapping the inclusive local date range in the household time zone, of every connected 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. When the viewer's own cache is older than the refresh interval, the request refreshes it from Google first; other adults' caches are refreshed by the worker. A failed refresh serves cached events with stale: true and is recorded. 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. The viewer's events come from the calendars they chose (see calendars on the connection); a grant without the calendar-list scope reads only the primary calendar. Every role may read it.
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.
GET /api/users/viewer/group-invitations
School’s Out users/viewer/group-invitations service.
Account or permitted machine credential required
PUT /api/users/viewer/group-invitations/{invitationId}
School’s Out users/viewer/group-invitations/{invitationId} service.
Account or permitted machine credential required
GET /api/users/viewer/groups
School’s Out users/viewer/groups service.
Account or permitted machine credential required
POST /api/users/viewer/groups
School’s Out users/viewer/groups service.
Account or permitted machine credential required
GET /api/users/viewer/groups/{groupId}
School’s Out users/viewer/groups/{groupId} service.
Account or permitted machine credential required
POST /api/users/viewer/groups/{groupId}/invitations
School’s Out users/viewer/groups/{groupId}/invitations service.
School’s Out users/viewer/groups/{groupId}/membership service.
Account or permitted machine credential required
GET /api/users/viewer/groups/{groupId}/messages
School’s Out users/viewer/groups/{groupId}/messages service.
Account or permitted machine credential required
POST /api/users/viewer/groups/{groupId}/messages
School’s Out users/viewer/groups/{groupId}/messages service.
Account or permitted machine credential required
PUT /api/users/viewer/groups/{groupId}/read
School’s Out users/viewer/groups/{groupId}/read service.
Account or permitted machine credential required
GET /api/users/viewer/home
GET answers Home: {needsYou, nextToPlan, updates, registrationAlerts, waitlist, featuredBookingId}.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
DELETE /api/users/viewer/home/dismissals/{itemId}
POST {until?} hides a Home item (for good without until); DELETE shows it again.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/home/dismissals/{itemId}
POST {until?} hides a Home item (for good without until); DELETE shows it again.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/home/updates/{updateId}/undo
POST reverts an update's effect and marks it undone; 409 when already undone or not undoable.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
DELETE /api/users/viewer/household
GET returns household search area, time zone, default budget, travel limits and caregiver availability with provenance. Version 0 means never saved. PATCH {version, changes} saves named preferences using the current version; null removes one. DELETE {version} erases preferences and provenance, retaining an empty record with an incremented version to reject stale edits. Read the current version before saving again. The household also carries emergency contacts, planning intent, a search radius and point (set from a places placeId) and the household referral {code, joined}. Feed instructions are each adult's own (/api/users/viewer/feed/instructions).
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
GET /api/users/viewer/household
GET returns household search area, time zone, default budget, travel limits and caregiver availability with provenance. Version 0 means never saved. PATCH {version, changes} saves named preferences using the current version; null removes one. DELETE {version} erases preferences and provenance, retaining an empty record with an incremented version to reject stale edits. Read the current version before saving again. The household also carries emergency contacts, planning intent, a search radius and point (set from a places placeId) and the household referral {code, joined}. Feed instructions are each adult's own (/api/users/viewer/feed/instructions).
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
PATCH /api/users/viewer/household
GET returns household search area, time zone, default budget, travel limits and caregiver availability with provenance. Version 0 means never saved. PATCH {version, changes} saves named preferences using the current version; null removes one. DELETE {version} erases preferences and provenance, retaining an empty record with an incremented version to reject stale edits. Read the current version before saving again. The household also carries emergency contacts, planning intent, a search radius and point (set from a places placeId) and the household referral {code, joined}. Feed instructions are each adult's own (/api/users/viewer/feed/instructions).
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
POST /api/users/viewer/household/confirmations
POST {version, keys} reconfirms named unchanged household preferences.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Private to that adult even for shared children.
GET /api/users/viewer/notification-preferences
GET returns the family's channel matrix (bookings, waitlist, schedule, groups, friends, assistant, and receipts by email only), quiet hours and product news switch (defaults at version 0). PUT {version, preferences} replaces them; a left-out groups, friends, assistant or receipts row, quietHours or productNews keeps its value; 409 on a stale version or SMS turned on without a verified phone.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/notification-preferences
GET returns the family's channel matrix (bookings, waitlist, schedule, groups, friends, assistant, and receipts by email only), quiet hours and product news switch (defaults at version 0). PUT {version, preferences} replaces them; a left-out groups, friends, assistant or receipts row, quietHours or productNews keeps its value; 409 on a stale version or SMS turned on without a verified phone.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/notifications
GET ?filter=all|bookings|unread&limit=&cursor= lists the family's notifications newest first with unreadCount and nextCursor.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/notifications/read
POST {ids} (up to 100) or {all: true, before} marks notifications read and returns unreadCount.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/payment-method
GET returns the default and backup cards, every saved card (cards, with the holder name), billing address and receipt email (paymentMethod repeats the default). PATCH {backupId?, makeDefault?, billingAddress?, receiptEmail?} chooses cards among the parent's own, saves the address, and sets where receipts are emailed (null: the account email; needs no card).
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PATCH /api/users/viewer/payment-method
GET returns the default and backup cards, every saved card (cards, with the holder name), billing address and receipt email (paymentMethod repeats the default). PATCH {backupId?, makeDefault?, billingAddress?, receiptEmail?} chooses cards among the parent's own, saves the address, and sets where receipts are emailed (null: the account email; needs no card).
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/payment-method/setup
Starts Stripe-hosted card setup. Send {returnUrl} with an allowlisted origin; returns {url}.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/payment-method/setup-intent
Starts in-page card setup for Stripe Elements: a SetupIntent on the parent's Stripe customer. Send {makeDefault?}; returns {clientSecret}. The next GET /api/users/viewer/payment-method saves the confirmed card.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
DELETE removes a saved card and detaches it in Stripe, first moving any booking hold on it to the remaining card; 409 when there is no card to move a hold to, the move is declined, or a hold is still being placed.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/payments
GET lists the family’s payments newest first, one per ledger transaction that moved money on the card or in the wallet (limit, cursor → nextCursor).
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/payments/{paymentId}/receipt
POST emails a fresh copy of one of the family’s payments’ receipts to its receipt email (202 {sentTo}); every booking payment, refund, top-up and gift card purchase is also emailed one as it is posted, unless receipts are turned off.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/places
GET geocodes q or reverse-geocodes latitude/longitude into up to 5 places with a served flag and a one-hour placeId for the household search area.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys. Rate-limited per account.
GET /api/users/viewer/plans
GET lists the viewer's plans, bookings merged with plans made here, by start date: ?childId=&from=&to=&group=&source=&activityId=&sessionId=&cursor=&limit= answers {plans, nextCursor}. POST adds an external or covered plan: {childIds, source, sessionId?, activityId?, title?, dates? | recurrence?, startTime?, endTime?, coveredBy?, status?}; an external plan of a session may omit dates and takes the session's days. An external plan of a session (or activity) is made at most once per child: children with an open one are not planned again (its status only moves forward), answering {plan, created, existingPlans} with 201 when a plan was made, else 200.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/plans
GET lists the viewer's plans, bookings merged with plans made here, by start date: ?childId=&from=&to=&group=&source=&activityId=&sessionId=&cursor=&limit= answers {plans, nextCursor}. POST adds an external or covered plan: {childIds, source, sessionId?, activityId?, title?, dates? | recurrence?, startTime?, endTime?, coveredBy?, status?}; an external plan of a session may omit dates and takes the session's days. An external plan of a session (or activity) is made at most once per child: children with an open one are not planned again (its status only moves forward), answering {plan, created, existingPlans} with 201 when a plan was made, else 200.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/plans/email-check
POST, when the parent asks, reads the viewer's own connected Gmail (last 60 days, at most 20 registration-like messages, headers and snippet only), matches each to an upcoming session of an organization the viewer saved or viewed and to their children, and records each match once per message as an unconfirmed external plan from email, which Home asks about. Answers {checked, added, unresolved}.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
DELETE /api/users/viewer/plans/{planId}
PATCH {status?: registered|applied|canceled, dates?, note?} changes a plan made here (applied again re-asks later); DELETE removes it. Booking plans answer 409.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PATCH /api/users/viewer/plans/{planId}
PATCH {status?: registered|applied|canceled, dates?, note?} changes a plan made here (applied again re-asks later); DELETE removes it. Booking plans answer 409.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/profile
GET reads the parent's public name, photo and privacy; PATCH changes privacy (versioned).
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PATCH /api/users/viewer/profile
GET reads the parent's public name, photo and privacy; PATCH changes privacy (versioned).
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
DELETE /api/users/viewer/profile/photo
PUT uploads a JPEG or PNG photo, stored as a 512×512 JPEG; DELETE removes it.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/profile/photo
PUT uploads a JPEG or PNG photo, stored as a 512×512 JPEG; DELETE removes it.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/provider-accounts
School’s Out users/viewer/provider-accounts service.
School’s Out users/viewer/provider-accounts/{platform} service.
Account or permitted machine credential required
DELETE /api/users/viewer/push-subscriptions
PUT {endpoint, keys: {p256dh, auth}} subscribes the sign-in session's browser to Web Push, or PUT {kind: expo, token, platform, deviceName?} its native app through Expo, replacing the session's previous one of that kind (an endpoint or token moves to the latest session). DELETE {endpoint} or {token} removes the family's own; 404 otherwise.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/push-subscriptions
PUT {endpoint, keys: {p256dh, auth}} subscribes the sign-in session's browser to Web Push, or PUT {kind: expo, token, platform, deviceName?} its native app through Expo, replacing the session's previous one of that kind (an endpoint or token moves to the latest session). DELETE {endpoint} or {token} removes the family's own; 404 otherwise.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/push-subscriptions/vapid-key
GET returns the VAPID publicKey browsers subscribe with, or null when push is off.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/referral-redemptions
POST redeems another household's referral code once, for new accounts; the wallet credits the referrer.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys.
GET /api/users/viewer/registrations
Lists the current parent’s latest 200 profile-backed registration disclosures, including camp, receipt, profile version, shared field names and exact accepted consent statements. Does not expose another parent’s records. Removing a reusable profile leaves its already-submitted camp registration and disclosure intact.
Current account only; machine, service and delegated-service credentials are rejected
POST /api/users/viewer/registrations/{id}
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.
Current account owns the child profile or has accepted active guardian access; cookie mutations require CSRF. Machine keys and service impersonation are rejected.
POST /api/users/viewer/reports
POST reports a person, listing or collection, once per target.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/saved
GET lists the parent's saved activities and organizations, newest first, keyset paged; keys answers "is this saved?" for up to 100 cards.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
DELETE /api/users/viewer/saved/{itemKey}
PUT saves (idempotent; savedAt restores an unsave within the undo window); DELETE unsaves and removes the item from the parent's collections, returning what Undo sends back.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/saved/{itemKey}
PUT saves (idempotent; savedAt restores an unsave within the undo window); DELETE unsaves and removes the item from the parent's collections, returning what Undo sends back.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
PUT /api/users/viewer/saved/{itemKey}/collections
PUT leaves the item saved and in exactly the given collections of the parent's.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/school-calendar
GET composes each child's school calendar (published days and the summer break, after the family's overrides) for from..to, at most 400 days.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/sessions
GET lists live device sessions, newest activity first, marking the cookie's own as current. A session names its device and kind from the sign-in's User-Agent, never a place: the API has no trustworthy coarse location.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys.
POST /api/users/viewer/sessions/revoke-others
POST revokes every live session except the current one and returns how many.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys.
DELETE /api/users/viewer/sessions/{sessionId}
DELETE revokes one of the viewer's sessions; cookies carrying its sid then answer 401.
Signed-in adult only; account cookie or account credential, never delegated service or machine keys.
GET /api/users/viewer/wallet
GET returns the wallet balance {balanceCents, currency}.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
GET /api/users/viewer/wallet/entries
GET lists wallet entries newest first (kind, limit, cursor → nextCursor).
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/wallet/redeem
Redeems a gift card code {code} into the viewer's wallet; returns {wallet, entry}. Unknown codes are rate limited per account.
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.
POST /api/users/viewer/wallet/top-ups
POST {idempotencyKey, amountCents 1000-50000, paymentMethod?} charges a saved card and credits the wallet on success; 402 requiresAction/declined, 503 unknown (resend the same request).
Current account only (cookie or account credential); machine keys and delegated service requests are rejected. Cookie mutations require CSRF.