[{"data":1,"prerenderedAt":3147},["ShallowReactive",2],{"www-founding":3,"$f0h4k8bMWAmH1U8FEZQERdIWhWCNTKc_jqArEfNRXbXc":7},{"total":4,"taken":5,"remaining":4,"active":6},100,0,true,{"openapi":8,"info":9,"servers":16,"security":23,"tags":26,"paths":75,"components":2617},"3.1.0",{"title":10,"version":11,"description":12,"contact":13},"PealCast API","1.0.0","The **PealCast** church-facing developer API — the follow-up engine for online church.\n\nPealCast turns your live stream's anonymous crowd into named people with a verified phone\nnumber, then hands your team a weekly **Shepherd's Report** of who to welcome (**New Faces**)\nand who to quietly check on (**Missed You**). This API lets you read that same data\nprogrammatically and manage your channels, audience, and go-live notifications.\n\n## Authentication\n\nEvery request is authenticated with an opaque **bearer token** and every token is pinned to\nexactly **ONE church** (organization). You create tokens in the app dashboard under\n**Developer \u002F API tokens**:\n\n- `pc_live_…` — a **production** token (talks to `https:\u002F\u002Fapi.pealcast.io`).\n- `pc_test_…` — a **non-production** token (talks to `https:\u002F\u002Fapi.dev.pealcast.io`).\n\nAccess is set **per token**: a **read** token can only call `GET` endpoints, while a\n**read\u002Fwrite** token may also create and update. A read-only token that calls a write\nendpoint receives `403 READ_ONLY_TOKEN`.\n\n## Player endpoints are public\n\nThe **Player (public)** endpoints power the watch page and take **no authentication** — they\nreturn only what a viewer's browser is allowed to see.\n\n## AI assistants (MCP)\n\nAn **MCP server** is available at **`mcp.pealcast.io`** so AI assistants (Claude and others)\ncan read your Shepherd's Report and audience with the same church-scoped token.\n",{"name":14,"url":15},"PealCast Support","https:\u002F\u002Fpealcast.io",[17,20],{"url":18,"description":19},"https:\u002F\u002Fapi.pealcast.io","Production",{"url":21,"description":22},"https:\u002F\u002Fapi.dev.pealcast.io","Development",[24],{"BearerAuth":25},[],[27,30,33,36,39,42,45,48,51,54,57,60,63,66,69,72],{"name":28,"description":29},"Audience","Your **owned, verified audience** — the real people (watchers\u002Fviewers) who have watched a\nstream, deduped by verified mobile number. A \"watcher\" (or \"viewer\") is a person who watched\nat least one broadcast.\n",{"name":31,"description":32},"Shepherd's Report","The weekly follow-up engine: **New Faces** (new watchers to welcome) and **Missed You**\n(drifting watchers to check on), each with a name and a verified phone number, plus the\ncontrols to send it on demand.\n",{"name":34,"description":35},"Channels","A church's streaming channels — **broadcast** (one-to-many service) or **conference**\n(two-way prayer line) — and their encoder\u002Fingest configuration.\n",{"name":37,"description":38},"Notifications","Go-live notifications and the delivery integrations behind them (our SMS\u002Femail, Planning\nCenter, custom domain) — who is subscribed and what has been sent.\n",{"name":40,"description":41},"Account","Your church's plan, trial\u002Fbilling state, and Shepherd's Report scheduling preferences.\n",{"name":43,"description":44},"Player (public)","Public, unauthenticated endpoints that power the watch page — live status, playback URL, and\nthe church's public channel list.\n",{"name":46,"description":47},"Auth","**Internal.** Passwordless phone\u002Femail + OTP sign-in and church-admin signup for the app,\nadmin, and watcher surfaces. Not part of the developer token contract.\n",{"name":49,"description":50},"Billing","**Internal.** The dashboard billing\u002Fcheckout flow — Stripe card capture, plan subscribe,\npre-pay account credit, and the billing summary. Not part of the developer token contract.\n",{"name":52,"description":53},"Developer keys","**Internal.** Session-only management of the church's REST developer keys and personal AI\n(MCP) connections. These endpoints require a human dashboard login — a `pc_` token can never\nmint or enumerate credentials.\n",{"name":55,"description":56},"Gate","**Internal.** The customizable capture gate — the church-side editor and the public\nwatcher-side prayer\u002Fconnect verification that identifies a viewer by verified mobile.\n",{"name":58,"description":59},"Planning Center","**Internal.** The confidential Planning Center OAuth flow (authorize URL + server-side code\nexchange) and the inbound PCO webhook for two-way sync.\n",{"name":61,"description":62},"Webhooks","**Internal.** Inbound webhooks from external providers — Cloudflare Stream (go-live\ndetection), SignalWire (SMS status + inbound), and Planning Center. Authenticated by HMAC\nsignature, never a bearer token.\n",{"name":64,"description":65},"Admin","**Internal.** The platform-admin (Smart Talk staff) surface — `actor='admin'` only.\n",{"name":67,"description":68},"Marketing","**Internal.** Public `\u002Fwww\u002F*` endpoints consumed by the marketing site — live Founding-100\nseat count, the pricing plan catalog, and the OpenAPI spec as JSON.\n",{"name":70,"description":71},"System","**Internal.** Operational endpoints — the health check used by CI deploy verification and\nuptime monitoring.\n",{"name":73,"description":74},"MCP","**Internal.** The Model Context Protocol JSON-RPC server (`mcp.pealcast.io`) that lets a\nchurch point its own AI assistant at its PealCast data with a church-scoped `pc_` token.\n",{"\u002Fapp\u002Fchannel-analytics":76,"\u002Fapp\u002Fchannel-viewers":102,"\u002Fapp\u002Fviewer":129,"\u002Fapp\u002Fchannel-archive":162,"\u002Fapp\u002Fchannels":190,"\u002Fapp\u002Fchannel":261,"\u002Fapp\u002Fchannel-notifications":279,"\u002Fapp\u002Fchannel-integrations":298,"\u002Fapp\u002Faccount":357,"\u002Fapp\u002Freport-settings":386,"\u002Fapp\u002Fsend-report":445,"\u002Fapp\u002Fsubscriber":468,"\u002Fapp\u002Fchannel-settings":520,"\u002Fapp\u002Forganization":570,"\u002Fplayer\u002Fchannel-playback":606,"\u002Fplayer\u002Forg":658,"\u002Fauth\u002Frequest-otp":701,"\u002Fauth\u002Fverify-otp":753,"\u002Fauth\u002Fme":808,"\u002Fauth\u002Flogout":825,"\u002Fauth\u002Fsignup-start":840,"\u002Fauth\u002Fsignup-verify-phone":879,"\u002Fauth\u002Fsignup":922,"\u002Fapp\u002Fbilling\u002Fsetup-intent":969,"\u002Fapp\u002Fbilling\u002Fsubscribe":999,"\u002Fbilling\u002Fsummary":1053,"\u002Fbilling\u002Fcredit":1084,"\u002Fapp\u002Fapi-tokens":1125,"\u002Fapp\u002Fmcp-connections":1225,"\u002Fapp\u002Fbootstrap":1324,"\u002Fapp\u002Forgs":1356,"\u002Fapp\u002Fsessions":1401,"\u002Fapp\u002Fsessions\u002Frevoke-others":1479,"\u002Fapp\u002Fstop-impersonation":1497,"\u002Fapp\u002Fswitch-org":1518,"\u002Fapp\u002Fusers":1548,"\u002Fapp\u002Ftheme":1623,"\u002Fapp\u002Fchannel-theme":1662,"\u002Fapp\u002Fchannel-gate":1730,"\u002Fapp\u002Fcustom-domain":1795,"\u002Fplanning-center\u002Fauthorize-url":1838,"\u002Fplanning-center\u002Fexchange":1866,"\u002Fplanning-center\u002Fwebhooks":1904,"\u002Fcloudflare\u002Fstream-webhook":1920,"\u002Fsignalwire\u002Fsms\u002Finbound":1934,"\u002Fsignalwire\u002Fsms\u002Fstatus":1953,"\u002Fsignalwire\u002Fcall\u002Fincoming":1967,"\u002Fsignalwire\u002Fcall\u002Frecording":1986,"\u002Fsignalwire\u002Fcall\u002Ftranscription":2001,"\u002Fsignalwire\u002Fcall\u002Fstatus":2016,"\u002Fsignalwire\u002Fcall\u002Ffail":2031,"\u002Fsignalwire\u002Fsms\u002Fsupport":2050,"\u002Fslack\u002Fevents":2068,"\u002Fstripe\u002Fwebhook":2089,"\u002Fplayer\u002Fgate\u002Fverify":2105,"\u002Fplayer\u002Fsubscribe\u002Finfo":2144,"\u002Fplayer\u002Fsubscribe\u002Fstart":2175,"\u002Fplayer\u002Fsubscribe\u002Fverify":2218,"\u002Fplayer\u002Fwatch-sessions\u002Fstart":2263,"\u002Fplayer\u002Fwatch-sessions\u002Fheartbeat":2310,"\u002Fwww\u002Fplans":2357,"\u002Fwww\u002Ffounding":2374,"\u002Fwww\u002Fopenapi":2397,"\u002Fadmin\u002Foverview":2415,"\u002Fadmin\u002Fstats":2441,"\u002Fadmin\u002Forgs":2457,"\u002Fadmin\u002Forg":2483,"\u002Fadmin\u002Fimpersonate":2508,"\u002Fmcp":2552,"\u002Fhealth":2593},{"get":77},{"operationId":78,"summary":79,"description":80,"tags":81,"parameters":82,"responses":89},"getShepherdsReport","Get a channel's Shepherd's Report and analytics","Returns the channel's rollup for the requested window: a summary (unique viewers, watch\nminutes, average QoE, broadcasts) plus the two Shepherd's Report queues —\n**`new_watchers`** (New Faces, people to welcome) and **`drifting_watchers`**\n(Missed You, regulars who have gone quiet).\n",[31],[83,85,87],{"$ref":84},"#\u002Fcomponents\u002Fparameters\u002FOrgParam",{"$ref":86},"#\u002Fcomponents\u002Fparameters\u002FSlugRequiredParam",{"$ref":88},"#\u002Fcomponents\u002Fparameters\u002FRangeParam",{"200":90,"401":96,"403":98,"404":100},{"description":91,"content":92},"The channel's analytics and Shepherd's Report queues.",{"application\u002Fjson":93},{"schema":94},{"$ref":95},"#\u002Fcomponents\u002Fschemas\u002FAnalytics",{"$ref":97},"#\u002Fcomponents\u002Fresponses\u002FUnauthenticated",{"$ref":99},"#\u002Fcomponents\u002Fresponses\u002FForbiddenOrg",{"$ref":101},"#\u002Fcomponents\u002Fresponses\u002FNoChannel",{"get":103},{"operationId":104,"summary":105,"description":106,"tags":107,"parameters":108,"responses":111},"listViewers","List a channel's viewers","Lists every known watcher for the channel — your owned, verified audience — with their\nfirst\u002Flast watch and total watch minutes.\n",[28],[109,110],{"$ref":84},{"$ref":86},{"200":112,"401":126,"403":127,"404":128},{"description":113,"content":114},"The channel's viewers.",{"application\u002Fjson":115},{"schema":116},{"type":117,"properties":118,"required":124},"object",{"viewers":119},{"type":120,"description":121,"items":122},"array","Every known watcher for the channel.",{"$ref":123},"#\u002Fcomponents\u002Fschemas\u002FViewerRow",[125],"viewers",{"$ref":97},{"$ref":99},{"$ref":101},{"get":130},{"operationId":131,"summary":132,"description":133,"tags":134,"parameters":135,"responses":144},"getViewer","Get one viewer's full profile","Returns a single watcher's complete profile: their identity, lifetime totals, every watch\nsession, any prayer\u002Fconnect submissions, and the go-live notifications they've received.\n",[28],[136,137,138],{"$ref":84},{"$ref":86},{"name":139,"in":140,"required":6,"description":141,"schema":142},"id","query","The subscriber (viewer) id.",{"type":143},"integer",{"200":145,"401":151,"403":152,"404":153},{"description":146,"content":147},"The viewer's full profile.",{"application\u002Fjson":148},{"schema":149},{"$ref":150},"#\u002Fcomponents\u002Fschemas\u002FViewerDetail",{"$ref":97},{"$ref":99},{"description":154,"content":155},"No such channel or viewer (`NO_CHANNEL` \u002F `NO_VIEWER`).",{"application\u002Fjson":156},{"schema":157,"example":159},{"$ref":158},"#\u002Fcomponents\u002Fschemas\u002FError",{"error":160,"code":161},"Viewer not found","NO_VIEWER",{"get":163},{"operationId":164,"summary":165,"description":166,"tags":167,"parameters":168,"responses":171},"listArchive","List a channel's recorded broadcasts","Lists the channel's recorded past broadcasts (VOD) within the plan's archive-retention\nwindow, with each recording's duration, playback URL, and watch stats.\n",[28],[169,170],{"$ref":84},{"$ref":86},{"200":172,"401":187,"403":188,"404":189},{"description":173,"content":174},"The channel's archived recordings.",{"application\u002Fjson":175},{"schema":176},{"type":117,"properties":177,"required":184},{"retention_days":178,"recordings":181},{"type":143,"description":179,"example":180},"How many days of archive this church's plan retains.",90,{"type":120,"items":182},{"$ref":183},"#\u002Fcomponents\u002Fschemas\u002FArchiveRecording",[185,186],"retention_days","recordings",{"$ref":97},{"$ref":99},{"$ref":101},{"get":191,"post":212},{"operationId":192,"summary":193,"description":194,"tags":195,"parameters":196,"responses":198},"listChannels","List the church's channels","Returns the church's channels as a lightweight navigation list (slug, name, type, live\nstatus).\n",[34],[197],{"$ref":84},{"200":199,"401":210,"403":211},{"description":200,"content":201},"The church's channels.",{"application\u002Fjson":202},{"schema":203},{"type":117,"properties":204,"required":208},{"channels":205},{"type":120,"items":206},{"$ref":207},"#\u002Fcomponents\u002Fschemas\u002FChannelNav",[209],"channels",{"$ref":97},{"$ref":99},{"operationId":213,"summary":214,"description":215,"tags":216,"parameters":217,"requestBody":219,"responses":235},"createChannel","Create a channel","Creates a new channel for the church and provisions its Cloudflare Stream live input.\n**Requires a read\u002Fwrite token** (a read-only token receives `403 READ_ONLY_TOKEN`). Fails\nwith `402 CHANNEL_LIMIT` when the church's plan channel allotment is already used.\n",[34],[218],{"$ref":84},{"required":6,"content":220},{"application\u002Fjson":221},{"schema":222},{"type":117,"properties":223,"required":233},{"name":224,"type":228},{"type":225,"description":226,"example":227},"string","Display name for the channel.","Main Service",{"type":225,"enum":229,"default":230,"description":232},[230,231],"broadcast","conference","`broadcast` = one-to-many service stream; `conference` = two-way prayer line.\n",[234],"name",{"200":236,"400":242,"401":250,"402":251,"403":259},{"description":237,"content":238},"The newly created channel.",{"application\u002Fjson":239},{"schema":240},{"$ref":241},"#\u002Fcomponents\u002Fschemas\u002FChannelApp",{"description":243,"content":244},"Invalid or missing name (`BAD_NAME`).",{"application\u002Fjson":245},{"schema":246,"example":247},{"$ref":158},{"error":248,"code":249},"Please provide a channel name","BAD_NAME",{"$ref":97},{"description":252,"content":253},"The plan's channel allotment is used up (`CHANNEL_LIMIT`).",{"application\u002Fjson":254},{"schema":255,"example":256},{"$ref":158},{"error":257,"code":258},"Your plan's channel limit has been reached","CHANNEL_LIMIT",{"$ref":260},"#\u002Fcomponents\u002Fresponses\u002FReadOnlyToken",{"get":262},{"operationId":263,"summary":264,"description":265,"tags":266,"parameters":267,"responses":270},"getChannel","Get one channel","Returns a single channel's full configuration, including its **stream_info** — the\nencoder\u002Fingest settings (RTMPS\u002FSRT URLs and keys, HLS playback URL) your church uses to go\nlive.\n",[34],[268,269],{"$ref":84},{"$ref":86},{"200":271,"401":276,"403":277,"404":278},{"description":272,"content":273},"The channel.",{"application\u002Fjson":274},{"schema":275},{"$ref":241},{"$ref":97},{"$ref":99},{"$ref":101},{"get":280},{"operationId":281,"summary":282,"description":283,"tags":284,"parameters":285,"responses":288},"getNotificationsOverview","Get a channel's notifications overview","Returns who is subscribed to go-live notifications for the channel, broken down by delivery\nintegration, plus recently sent notifications and recent unsubscribes.\n",[37],[286,287],{"$ref":84},{"$ref":86},{"200":289,"401":295,"403":296,"404":297},{"description":290,"content":291},"The channel's notifications overview.",{"application\u002Fjson":292},{"schema":293},{"$ref":294},"#\u002Fcomponents\u002Fschemas\u002FNotificationsOverview",{"$ref":97},{"$ref":99},{"$ref":101},{"get":299},{"operationId":300,"summary":301,"description":302,"tags":303,"parameters":304,"responses":307},"getChannelIntegrations","Get a channel's connected integrations","Returns the channel's connected systems — Planning Center status, custom-domain\nentitlement\u002Fhostname, and the list of notification-delivery integrations available to this\nchurch.\n",[37],[305,306],{"$ref":84},{"$ref":86},{"200":308,"401":354,"403":355,"404":356},{"description":309,"content":310},"The channel's integration status.",{"application\u002Fjson":311},{"schema":312},{"type":117,"properties":313,"required":350},{"planning_center":314,"custom_domain":328,"available_notification_integrations":339},{"type":117,"description":315,"properties":316},"Planning Center connection status for this church.",{"connected":317,"status":319,"display_name":321,"last_synced_at":325},{"type":318},"boolean",{"type":225,"example":320},"connected",{"type":322,"example":324},[225,323],"null","First Baptist (Planning Center)",{"type":326,"format":327},[225,323],"date-time",{"type":117,"description":329,"properties":330},"Custom watch\u002Fnotification domain entitlement and DNS target.",{"entitled":331,"hostname":333,"cname_target":336},{"type":318,"description":332},"Whether the plan includes a custom domain.",{"type":334,"example":335},[225,323],"watch.firstbaptist.org",{"type":337,"example":338},[225,323],"player.pealcast.io",{"type":120,"description":340,"items":341},"The notification-delivery integrations selectable by this church.",{"type":117,"properties":342},{"key":343,"label":345,"medium":347,"available":349},{"type":225,"example":344},"native.sms",{"type":225,"example":346},"Text message",{"type":225,"example":348},"sms",{"type":318},[351,352,353],"planning_center","custom_domain","available_notification_integrations",{"$ref":97},{"$ref":99},{"$ref":101},{"get":358},{"operationId":359,"summary":360,"description":361,"tags":362,"parameters":363,"responses":365},"getAccount","Get the church's account and plans","Returns the church's billing\u002Ftrial state, the catalog of plans, and the Stripe publishable\nkey used by the dashboard to collect a card.\n",[40],[364],{"$ref":84},{"200":366,"401":384,"403":385},{"description":367,"content":368},"The account state, plan catalog, and Stripe publishable key.",{"application\u002Fjson":369},{"schema":370},{"type":117,"properties":371,"required":380},{"account":372,"plans":374,"publishable_key":377},{"$ref":373},"#\u002Fcomponents\u002Fschemas\u002FAccountState",{"type":120,"items":375},{"$ref":376},"#\u002Fcomponents\u002Fschemas\u002FPlan",{"type":225,"description":378,"example":379},"The Stripe publishable key (safe for the browser).","pk_live_51ABC…",[381,382,383],"account","plans","publishable_key",{"$ref":97},{"$ref":99},{"get":387,"post":415},{"operationId":388,"summary":389,"description":390,"tags":391,"parameters":392,"responses":394},"getReportSettings","Get Shepherd's Report schedule settings","Returns when the church's weekly Shepherd's Report is delivered (day, hour, timezone) plus\nthe list of selectable timezones.\n",[40],[393],{"$ref":84},{"200":395,"401":413,"403":414},{"description":396,"content":397},"The current report schedule and available timezones.",{"application\u002Fjson":398},{"schema":399},{"allOf":400},[401,403],{"$ref":402},"#\u002Fcomponents\u002Fschemas\u002FReportSettings",{"type":117,"properties":404,"required":411},{"timezones":405},{"type":120,"description":406,"items":407,"example":408},"Selectable IANA timezone names.",{"type":225},[409,410],"America\u002FNew_York","America\u002FChicago",[412],"timezones",{"$ref":97},{"$ref":99},{"operationId":416,"summary":417,"description":418,"tags":419,"parameters":420,"requestBody":422,"responses":437},"updateReportSettings","Update Shepherd's Report schedule settings","Updates when the weekly Shepherd's Report is delivered. Any omitted field is left unchanged.\n**Requires a read\u002Fwrite token** (a read-only token receives `403 READ_ONLY_TOKEN`).\n",[40],[421],{"$ref":84},{"required":6,"content":423},{"application\u002Fjson":424},{"schema":425},{"type":117,"properties":426},{"enabled":427,"wday":429,"hour":432,"timezone":435},{"type":318,"description":428},"Whether the weekly report is sent at all.",{"type":143,"minimum":5,"maximum":430,"description":431},6,"Day of week (0 = Sunday … 6 = Saturday).",{"type":143,"minimum":5,"maximum":433,"description":434},23,"Hour of day (local to `timezone`).",{"type":225,"description":436,"example":409},"IANA timezone name.",{"200":438,"401":443,"403":444},{"description":439,"content":440},"The updated report settings.",{"application\u002Fjson":441},{"schema":442},{"$ref":402},{"$ref":97},{"$ref":260},{"post":446},{"operationId":447,"summary":448,"description":449,"tags":450,"parameters":451,"responses":453},"sendShepherdsReport","Send the Shepherd's Report now","Sends the church's Shepherd's Report immediately (out of cadence) to the online-ministry\nteam. **Requires a read\u002Fwrite token** (a read-only token receives `403 READ_ONLY_TOKEN`).\n",[31],[452],{"$ref":84},{"200":454,"401":466,"403":467},{"description":455,"content":456},"The report was queued\u002Fsent.",{"application\u002Fjson":457},{"schema":458},{"type":117,"properties":459,"required":463},{"ok":460,"message":461},{"type":318,"const":6},{"type":225,"example":462},"Your Shepherd's Report is on its way.",[464,465],"ok","message",{"$ref":97},{"$ref":260},{"post":469},{"operationId":470,"summary":471,"description":472,"tags":473,"parameters":474,"requestBody":476,"responses":490},"updateSubscriber","Update an audience member","Updates an audience member's (subscriber's) editable details. The person's **verified\nmobile number cannot be changed** here — it is the dedupe key for your owned audience. Pass\nan empty string to clear a field. **Requires a read\u002Fwrite token** (a read-only token\nreceives `403 READ_ONLY_TOKEN`).\n",[28],[475],{"$ref":84},{"required":6,"content":477},{"application\u002Fjson":478},{"schema":479},{"type":117,"properties":480,"required":489},{"id":481,"first_name":483,"last_name":485,"email":487},{"type":143,"description":482},"The subscriber (audience member) id.",{"type":225,"description":484},"The member's first name. Empty string clears it.",{"type":225,"description":486},"The member's last name. Empty string clears it.",{"type":225,"description":488},"The member's email. Empty string clears it.",[139],{"200":491,"401":502,"403":503,"404":504,"422":512},{"description":492,"content":493},"The updated audience member.",{"application\u002Fjson":494},{"schema":495},{"type":117,"properties":496,"required":500},{"ok":497,"subscriber":498},{"type":318,"const":6},{"$ref":499},"#\u002Fcomponents\u002Fschemas\u002FSubscriber",[464,501],"subscriber",{"$ref":97},{"$ref":260},{"description":505,"content":506},"No such audience member for this church (`NO_SUBSCRIBER`).",{"application\u002Fjson":507},{"schema":508,"example":509},{"$ref":158},{"error":510,"code":511},"Audience member not found","NO_SUBSCRIBER",{"description":513,"content":514},"Invalid field value (`INVALID`).",{"application\u002Fjson":515},{"schema":516,"example":517},{"$ref":158},{"error":518,"code":519},"That email address doesn't look right","INVALID",{"post":521},{"operationId":522,"summary":523,"description":524,"tags":525,"parameters":526,"requestBody":528,"responses":550},"updateChannelSettings","Update a channel's settings","Updates a channel's editable settings. The channel's **slug and type are structural and\nare not editable here**. Any omitted field is left unchanged. **Requires a read\u002Fwrite\ntoken** (a read-only token receives `403 READ_ONLY_TOKEN`).\n",[34],[527],{"$ref":84},{"required":6,"content":529},{"application\u002Fjson":530},{"schema":531},{"type":117,"properties":532,"required":548},{"slug":533,"name":536,"description":537,"timezone":539,"gate_enabled":540,"gate_free_watch_seconds":542,"recording_retention_days":544},{"type":225,"description":534,"example":535},"The channel slug (identifies which channel to update).","main-service",{"type":225,"description":226},{"type":225,"description":538},"A short public description of the channel.",{"type":225,"description":436,"example":409},{"type":318,"description":541},"Whether the capture gate is active for this channel.",{"type":143,"description":543},"Seconds a viewer may watch before the gate prompts.",{"type":143,"description":545,"minimum":546,"example":547},"Recording auto-delete window (days). Clamped to [30, the plan's archive cap]; the change is pushed to Cloudflare as deleteRecordingAfterDays.",30,60,[549],"slug",{"200":551,"401":561,"403":562,"404":563,"422":564},{"description":552,"content":553},"The updated channel.",{"application\u002Fjson":554},{"schema":555},{"type":117,"properties":556,"required":559},{"ok":557,"channel":558},{"type":318,"const":6},{"$ref":241},[464,560],"channel",{"$ref":97},{"$ref":260},{"$ref":101},{"description":513,"content":565},{"application\u002Fjson":566},{"schema":567,"example":568},{"$ref":158},{"error":569,"code":519},"That isn't a valid timezone",{"post":571},{"operationId":572,"summary":573,"description":574,"tags":575,"parameters":576,"requestBody":578,"responses":587},"updateOrganization","Update the church's details","Updates the church's (organization's) editable details. **Billing, status, plan, and slug\nare not editable here.** Any omitted field is left unchanged. **Requires a read\u002Fwrite\ntoken** (a read-only token receives `403 READ_ONLY_TOKEN`).\n",[40],[577],{"$ref":84},{"required":6,"content":579},{"application\u002Fjson":580},{"schema":581},{"type":117,"properties":582},{"name":583,"timezone":586},{"type":225,"description":584,"example":585},"The church's display name.","First Baptist",{"type":225,"description":436,"example":409},{"200":588,"401":599,"403":600,"422":601},{"description":589,"content":590},"The updated church.",{"application\u002Fjson":591},{"schema":592},{"type":117,"properties":593,"required":597},{"ok":594,"organization":595},{"type":318,"const":6},{"$ref":596},"#\u002Fcomponents\u002Fschemas\u002FOrgApp",[464,598],"organization",{"$ref":97},{"$ref":260},{"description":513,"content":602},{"application\u002Fjson":603},{"schema":604,"example":605},{"$ref":158},{"error":569,"code":519},{"get":607},{"operationId":608,"summary":609,"description":610,"tags":611,"security":612,"parameters":613,"responses":628},"getPlayback","Get a channel's live playback state","**Public (no auth).** Returns the current live state and playback URL for a channel, plus\nthe gate configuration and the notification methods a viewer can opt into. This is what the\nwatch page calls to render the player.\n",[43],[],[614,618,623,627],{"name":615,"in":140,"required":6,"description":616,"schema":617},"c","The channel slug or id.",{"type":225},{"name":619,"in":140,"required":620,"description":621,"schema":622},"t",false,"A signed watch token that pre-identifies the viewer (from a notification link).",{"type":225},{"name":624,"in":140,"required":620,"description":625,"schema":626},"host","The watch host (used to resolve a custom domain).",{"type":225},{"$ref":84},{"200":629,"404":657},{"description":630,"content":631},"The channel's playback state.",{"application\u002Fjson":632},{"schema":633},{"type":117,"properties":634,"required":651},{"is_live":635,"broadcast_id":636,"hls_url":638,"gate_enabled":641,"gate_free_watch_seconds":642,"notification_methods":643,"theme":649},{"type":318},{"type":637},[143,323],{"type":639,"description":640},[225,323],"The LL-HLS playback URL, or null when not live.",{"type":318,"description":541},{"type":143,"description":543},{"type":120,"items":644},{"type":117,"properties":645},{"key":646,"label":647,"medium":648},{"type":225,"example":344},{"type":225,"example":346},{"type":225,"example":348},{"type":117,"description":650,"additionalProperties":6},"Channel\u002Forg theming (brand colors, logo) for the player.",[652,653,654,655,656],"is_live","gate_enabled","gate_free_watch_seconds","notification_methods","theme",{"$ref":101},{"get":659},{"operationId":660,"summary":661,"description":662,"tags":663,"security":664,"parameters":665,"responses":671},"getPlayerOrg","Get a church's public player profile","**Public (no auth).** Resolves a church by custom-domain host or slug and returns its public\nprofile plus the list of channels a viewer can watch.\n",[43],[],[666,668],{"name":624,"in":140,"required":620,"description":625,"schema":667},{"type":225},{"name":549,"in":140,"required":620,"description":669,"schema":670},"The church (organization) slug.",{"type":225},{"200":672,"404":699},{"description":673,"content":674},"The church's public player profile and channels.",{"application\u002Fjson":675},{"schema":676},{"type":117,"properties":677,"required":697},{"org":678,"channels":686},{"type":117,"properties":679,"required":685},{"name":680,"slug":681,"custom_domain":682,"theme":684},{"type":225},{"type":225},{"type":683},[225,323],{"type":117,"additionalProperties":6},[234,549],{"type":120,"items":687},{"type":117,"properties":688,"required":695},{"slug":689,"name":690,"type":691,"is_live":693,"theme":694},{"type":225},{"type":225},{"type":225,"enum":692},[230,231],{"type":318},{"type":117,"additionalProperties":6},[549,234,696,652],"type",[698,209],"org",{"$ref":700},"#\u002Fcomponents\u002Fresponses\u002FNoOrg",{"post":702},{"operationId":703,"summary":704,"x-internal":6,"description":705,"tags":706,"security":707,"requestBody":708,"responses":727},"authRequestOtp","Request a login OTP","**Internal, no auth.** Sends a one-time code to a US\u002FCanada mobile (SMS) or an email for\nthe given `actor` (`admin` | `app` | `watcher`). The single `identifier` field accepts a\ncell number OR an email; watchers must supply a real, textable mobile.\n",[46],[],{"required":6,"content":709},{"application\u002Fjson":710},{"schema":711},{"type":117,"properties":712,"required":724},{"identifier":713,"phone":715,"actor":717,"channel":722},{"type":225,"description":714},"A US\u002FCanada mobile number or an email. (Alias, `phone`, also accepted.)",{"type":225,"description":716},"Deprecated alias for `identifier`.",{"type":225,"enum":718},[719,720,721],"admin","app","watcher",{"type":225,"description":723},"Optional channel slug\u002Fid (watcher opt-in context).",[725,726],"identifier","actor",{"200":728,"400":743,"429":748},{"description":729,"content":730},"A code was created and delivery attempted.",{"application\u002Fjson":731},{"schema":732},{"type":117,"properties":733,"required":742},{"ok":734,"ttl_seconds":735,"via":736,"dev_code":740},{"type":318,"const":6},{"type":143},{"type":225,"enum":737},[738,739],"mobile","email",{"type":225,"description":741},"DEV ONLY — the code, returned when delivery isn't wired up in dev.",[464],{"description":744,"content":745},"Bad actor, identifier, non-mobile watcher, or undeliverable email.",{"application\u002Fjson":746},{"schema":747},{"$ref":158},{"description":749,"content":750},"Too many codes requested (`RATE_LIMITED`).",{"application\u002Fjson":751},{"schema":752},{"$ref":158},{"post":754},{"operationId":755,"summary":756,"x-internal":6,"description":757,"tags":758,"security":759,"requestBody":760,"responses":772},"authVerifyOtp","Verify an OTP and mint a session","**Internal, no auth.** Verifies the code for the `actor` and, on success, issues a bearer\nsession token (a JWT backed by a revocable `auth_sessions` row). A paid, active church gets\na long session; a trial\u002Fpast-due church gets a short 15-minute one (the paywall).\n",[46],[],{"required":6,"content":761},{"application\u002Fjson":762},{"schema":763},{"type":117,"properties":764,"required":770},{"identifier":765,"phone":766,"code":767,"actor":768},{"type":225},{"type":225,"description":716},{"type":225},{"type":225,"enum":769},[719,720,721],[725,771,726],"code",{"200":773,"400":788,"401":793,"403":798,"404":803},{"description":774,"content":775},"The session token and the signed-in profile.",{"application\u002Fjson":776},{"schema":777},{"type":117,"properties":778,"required":786},{"ok":779,"token":780,"actor":781,"expires_at":783,"profile":785},{"type":318,"const":6},{"type":225},{"type":225,"enum":782},[719,720,721],{"type":784,"format":327},[225,323],{"type":117,"additionalProperties":6},[464,787,726],"token",{"description":789,"content":790},"Bad actor or identifier (`BAD_ACTOR` \u002F `BAD_IDENTIFIER`).",{"application\u002Fjson":791},{"schema":792},{"$ref":158},{"description":794,"content":795},"Invalid or expired code (`BAD_CODE`).",{"application\u002Fjson":796},{"schema":797},{"$ref":158},{"description":799,"content":800},"The identity isn't allowed for this actor (`NOT_ALLOWED`).",{"application\u002Fjson":801},{"schema":802},{"$ref":158},{"description":804,"content":805},"A watcher with no prior identity must join through the gate (`NEEDS_GATE`).",{"application\u002Fjson":806},{"schema":807},{"$ref":158},{"get":809},{"operationId":810,"summary":811,"x-internal":6,"description":812,"tags":813,"responses":814},"authMe","Validate the current session","**Internal.** Returns the actor + profile for the caller's **session bearer token** (a\nsigned-in JWT, not a `pc_` developer token). The front-ends call this on load to validate a\nstored token.\n",[46],{"200":815,"401":824},{"description":816,"content":817},"The current actor and profile.",{"application\u002Fjson":818},{"schema":819},{"type":117,"properties":820,"required":823},{"actor":821,"profile":822},{"type":225},{"type":117,"additionalProperties":6},[726],{"$ref":97},{"post":826},{"operationId":827,"summary":828,"x-internal":6,"description":829,"tags":830,"responses":831},"authLogout","Revoke the current session","**Internal.** Revokes the caller's `auth_sessions` row so the (long-lived) token stops\nworking immediately.\n",[46],{"200":832},{"description":833,"content":834},"The session was revoked (idempotent).",{"application\u002Fjson":835},{"schema":836},{"type":117,"properties":837,"required":839},{"ok":838},{"type":318,"const":6},[464],{"post":841},{"operationId":842,"summary":843,"x-internal":6,"description":844,"tags":845,"security":846,"requestBody":847,"responses":857},"authSignupStart","Church-admin signup — step 1 (validate + text a code)","**Internal, no auth.** Step 1 of the 3-step church-admin signup: validates name, a real\nmobile, and a deliverable email, then texts a phone OTP.\n",[46],[],{"required":6,"content":848},{"application\u002Fjson":849},{"schema":850},{"type":117,"properties":851,"required":855},{"name":852,"email":853,"phone":854},{"type":225},{"type":225},{"type":225},[234,739,856],"phone",{"200":858,"400":869,"429":874},{"description":859,"content":860},"The phone code was sent.",{"application\u002Fjson":861},{"schema":862},{"type":117,"properties":863,"required":868},{"ok":864,"via":865,"dev_code":866},{"type":318,"const":6},{"type":225,"const":738},{"type":225,"description":867},"DEV ONLY.",[464],{"description":870,"content":871},"Missing\u002Finvalid name, phone, or email.",{"application\u002Fjson":872},{"schema":873},{"$ref":158},{"description":875,"content":876},"Rate limited (`RATE_LIMITED`).",{"application\u002Fjson":877},{"schema":878},{"$ref":158},{"post":880},{"operationId":881,"summary":882,"x-internal":6,"description":883,"tags":884,"security":885,"requestBody":886,"responses":895},"authSignupVerifyPhone","Church-admin signup — step 2 (verify phone, email a code)","**Internal, no auth.** Step 2: verifies the phone OTP; on success emails an OTP and returns\na `phone_proof` to carry into step 3.\n",[46],[],{"required":6,"content":887},{"application\u002Fjson":888},{"schema":889},{"type":117,"properties":890,"required":894},{"phone":891,"email":892,"code":893},{"type":225},{"type":225},{"type":225},[856,739,771],{"200":896,"400":908,"401":913,"429":918},{"description":897,"content":898},"Phone proven; email code sent.",{"application\u002Fjson":899},{"schema":900},{"type":117,"properties":901,"required":906},{"ok":902,"via":903,"phone_proof":904,"dev_code":905},{"type":318,"const":6},{"type":225,"const":739},{"type":225},{"type":225,"description":867},[464,907],"phone_proof",{"description":909,"content":910},"Invalid phone or email.",{"application\u002Fjson":911},{"schema":912},{"$ref":158},{"description":914,"content":915},"Wrong phone code (`BAD_CODE`).",{"application\u002Fjson":916},{"schema":917},{"$ref":158},{"description":875,"content":919},{"application\u002Fjson":920},{"schema":921},{"$ref":158},{"post":923},{"operationId":924,"summary":925,"x-internal":6,"description":926,"tags":927,"security":928,"requestBody":929,"responses":942},"authSignup","Church-admin signup — step 3 (create the account)","**Internal, no auth.** Step 3: verifies the `phone_proof` + email code, then creates (or\nreactivates) the user with a verified mobile + email, stands up their church, and makes them\nowner (a 14-day, card-free trial of the Large plan).\n",[46],[],{"required":6,"content":930},{"application\u002Fjson":931},{"schema":932},{"type":117,"properties":933,"required":941},{"name":934,"email":935,"phone":936,"phone_proof":937,"code":938,"email_code":940},{"type":225},{"type":225},{"type":225},{"type":225},{"type":225,"description":939},"The email OTP. (Alias `email_code` also accepted.)",{"type":225},[234,739,856,907],{"200":943,"201":949,"400":954,"401":959,"422":964},{"description":944,"content":945},"An existing account was signed in.",{"application\u002Fjson":946},{"schema":947},{"$ref":948},"#\u002Fcomponents\u002Fschemas\u002FSignupResult",{"description":950,"content":951},"A new account was created.",{"application\u002Fjson":952},{"schema":953},{"$ref":948},{"description":955,"content":956},"Missing signup details (`BAD_REQUEST`).",{"application\u002Fjson":957},{"schema":958},{"$ref":158},{"description":960,"content":961},"Expired phone proof or wrong email code (`PHONE_UNVERIFIED` \u002F `BAD_CODE`).",{"application\u002Fjson":962},{"schema":963},{"$ref":158},{"description":965,"content":966},"Could not create the account (`INVALID`).",{"application\u002Fjson":967},{"schema":968},{"$ref":158},{"post":970},{"operationId":971,"summary":972,"x-internal":6,"description":973,"tags":974,"parameters":975,"responses":977},"createBillingSetupIntent","Start card entry (Stripe SetupIntent)","**Internal.** Ensures a Stripe customer for the church and returns a SetupIntent\n`client_secret` so the dashboard can collect a card with Stripe.js. Owner\u002Fadmin only.\n",[49],[976],{"$ref":84},{"200":978,"401":988,"403":989,"500":994},{"description":979,"content":980},"The SetupIntent client secret + publishable key.",{"application\u002Fjson":981},{"schema":982},{"type":117,"properties":983,"required":986},{"client_secret":984,"publishable_key":985},{"type":225},{"type":225},[987,383],"client_secret",{"$ref":97},{"description":990,"content":991},"Only owners\u002Fadmins can manage billing (`FORBIDDEN`).",{"application\u002Fjson":992},{"schema":993},{"$ref":158},{"description":995,"content":996},"Billing isn't configured or the SetupIntent failed.",{"application\u002Fjson":997},{"schema":998},{"$ref":158},{"post":1000},{"operationId":1001,"summary":1002,"x-internal":6,"description":1003,"tags":1004,"parameters":1005,"requestBody":1007,"responses":1023},"subscribeToPlan","Set the card as default and start the plan","**Internal.** Saves the chosen card as the customer's default, points the church at the\nchosen plan\u002Finterval, and runs the in-house billing system (invoice + off-session charge).\nA decline leaves the church `past_due`. Owner\u002Fadmin only.\n",[49],[1006],{"$ref":84},{"required":6,"content":1008},{"application\u002Fjson":1009},{"schema":1010},{"type":117,"properties":1011,"required":1020},{"plan_code":1012,"payment_method_id":1014,"interval":1016},{"type":225,"example":1013},"medium",{"type":225,"description":1015},"The Stripe PaymentMethod id from the card element.",{"type":225,"enum":1017,"default":1018},[1018,1019],"monthly","annual",[1021,1022],"plan_code","payment_method_id",{"200":1024,"400":1033,"401":1038,"402":1039,"403":1044,"500":1048},{"description":1025,"content":1026},"Charged — the church is now active.",{"application\u002Fjson":1027},{"schema":1028},{"type":117,"properties":1029,"required":1032},{"ok":1030,"account":1031},{"type":318,"const":6},{"$ref":373},[464,381],{"description":1034,"content":1035},"No plan chosen or no card (`BAD_PLAN` \u002F `NO_CARD`).",{"application\u002Fjson":1036},{"schema":1037},{"$ref":158},{"$ref":97},{"description":1040,"content":1041},"The card was declined (`CARD_ERROR` \u002F `CHARGE_FAILED`).",{"application\u002Fjson":1042},{"schema":1043},{"$ref":158},{"description":990,"content":1045},{"application\u002Fjson":1046},{"schema":1047},{"$ref":158},{"description":1049,"content":1050},"Billing isn't configured (`STRIPE_UNCONFIGURED`).",{"application\u002Fjson":1051},{"schema":1052},{"$ref":158},{"get":1054},{"operationId":1055,"summary":1056,"x-internal":6,"description":1057,"tags":1058,"responses":1059},"getBillingSummary","Get the billing page summary","**Internal.** Powers the app billing page — plan\u002Fstatus\u002Fnext charge, credit balance + bonus\noffer, whether a card is on file, and recent invoices. Church-admin or platform-admin.\n",[49],{"200":1060,"401":1078,"404":1079},{"description":1061,"content":1062},"The billing summary.",{"application\u002Fjson":1063},{"schema":1064},{"type":117,"properties":1065,"required":1074},{"organization":1066,"plan":1067,"credit_balance_cents":1069,"credit_bonus":1070,"has_payment_method":1071,"invoices":1072},{"type":117,"additionalProperties":6},{"type":1068,"additionalProperties":6},[117,323],{"type":143},{"type":117,"additionalProperties":6},{"type":318},{"type":120,"items":1073},{"type":117,"additionalProperties":6},[598,1075,1076,1077],"credit_balance_cents","has_payment_method","invoices",{"$ref":97},{"description":1080,"content":1081},"No organization on the session (`NO_ORG`).",{"application\u002Fjson":1082},{"schema":1083},{"$ref":158},{"post":1085},{"operationId":1086,"summary":1087,"x-internal":6,"description":1088,"tags":1089,"requestBody":1090,"responses":1098},"addBillingCredit","Pre-pay account credit","**Internal.** Charges the card on file and adds account credit (a +10% bonus on $500+).\nCredit is drawn down first when the monthly invoice is charged. Church-admin or\nplatform-admin.\n",[49],{"required":6,"content":1091},{"application\u002Fjson":1092},{"schema":1093},{"type":117,"properties":1094,"required":1096},{"amount_cents":1095},{"type":143},[1097],"amount_cents",{"200":1099,"400":1110,"401":1115,"402":1116,"404":1121},{"description":1100,"content":1101},"The credit was added.",{"application\u002Fjson":1102},{"schema":1103},{"type":117,"properties":1104,"required":1109},{"ok":1105,"charged_cents":1106,"bonus_cents":1107,"balance_cents":1108},{"type":318,"const":6},{"type":143},{"type":143},{"type":143},[464],{"description":1111,"content":1112},"Bad amount or no card (`BAD_AMOUNT` \u002F `NO_PAYMENT_METHOD`).",{"application\u002Fjson":1113},{"schema":1114},{"$ref":158},{"$ref":97},{"description":1117,"content":1118},"The card was declined (`CHARGE_FAILED`).",{"application\u002Fjson":1119},{"schema":1120},{"$ref":158},{"description":1080,"content":1122},{"application\u002Fjson":1123},{"schema":1124},{"$ref":158},{"get":1126,"post":1148,"delete":1193},{"operationId":1127,"summary":1128,"x-internal":6,"description":1129,"tags":1130,"parameters":1131,"responses":1133},"listApiTokens","List the church's developer keys","**Internal, session-only.** Lists the org's REST developer keys (secrets never returned).\nRequires a human dashboard login (a `pc_` token gets `403 SESSION_REQUIRED`); owner\u002Fadmin\nonly.\n",[52],[1132],{"$ref":84},{"200":1134,"401":1145,"403":1146},{"description":1135,"content":1136},"The developer keys.",{"application\u002Fjson":1137},{"schema":1138},{"type":117,"properties":1139,"required":1143},{"tokens":1140},{"type":120,"items":1141},{"$ref":1142},"#\u002Fcomponents\u002Fschemas\u002FApiTokenInfo",[1144],"tokens",{"$ref":97},{"$ref":1147},"#\u002Fcomponents\u002Fresponses\u002FSessionOrRole",{"operationId":1149,"summary":1150,"x-internal":6,"description":1151,"tags":1152,"parameters":1153,"requestBody":1155,"responses":1168},"createApiToken","Mint a developer key","**Internal, session-only.** Mints an org REST developer key and returns the plaintext token\n**once** (unrecoverable after). Requires a human login; owner\u002Fadmin only.\n",[52],[1154],{"$ref":84},{"required":6,"content":1156},{"application\u002Fjson":1157},{"schema":1158},{"type":117,"properties":1159,"required":1167},{"name":1160,"access":1161,"expires_in_days":1165},{"type":225},{"type":225,"enum":1162,"default":1163},[1163,1164],"read","read_write",{"type":143,"description":1166},"Optional; omit or 0 = never expires.",[234],{"200":1169,"400":1181,"401":1186,"403":1187,"422":1188},{"description":1170,"content":1171},"The newly minted key (plaintext token returned once).",{"application\u002Fjson":1172},{"schema":1173},{"type":117,"properties":1174,"required":1179},{"ok":1175,"token":1176,"api_token":1178},{"type":318,"const":6},{"type":225,"description":1177},"The plaintext token — shown only in this response.",{"$ref":1142},[464,787,1180],"api_token",{"description":1182,"content":1183},"Missing name (`BAD_NAME`).",{"application\u002Fjson":1184},{"schema":1185},{"$ref":158},{"$ref":97},{"$ref":1147},{"description":1189,"content":1190},"Could not create the key (`INVALID`).",{"application\u002Fjson":1191},{"schema":1192},{"$ref":158},{"operationId":1194,"summary":1195,"x-internal":6,"description":1196,"tags":1197,"parameters":1198,"responses":1203},"revokeApiToken","Revoke a developer key","**Internal, session-only.** Revokes an org developer key by id. Requires a human login;\nowner\u002Fadmin only.\n",[52],[1199,1200],{"$ref":84},{"name":139,"in":140,"required":6,"description":1201,"schema":1202},"The developer key id.",{"type":143},{"200":1204,"400":1213,"401":1218,"403":1219,"404":1220},{"description":1205,"content":1206},"The key was revoked.",{"application\u002Fjson":1207},{"schema":1208},{"type":117,"properties":1209,"required":1212},{"ok":1210,"api_token":1211},{"type":318,"const":6},{"$ref":1142},[464],{"description":1214,"content":1215},"Missing id (`BAD_ID`).",{"application\u002Fjson":1216},{"schema":1217},{"$ref":158},{"$ref":97},{"$ref":1147},{"description":1221,"content":1222},"No such key (`NO_TOKEN`).",{"application\u002Fjson":1223},{"schema":1224},{"$ref":158},{"get":1226,"post":1252,"delete":1289},{"operationId":1227,"summary":1228,"x-internal":6,"description":1229,"tags":1230,"parameters":1231,"responses":1233},"listMcpConnections","List my AI (MCP) connections","**Internal, session-only.** Lists the caller's OWN personal AI (MCP) connections in this\norg, plus the MCP endpoint URL and whether the caller's role can create a write-capable\nconnection. Requires a human login.\n",[52],[1232],{"$ref":84},{"200":1234,"401":1249,"403":1250},{"description":1235,"content":1236},"The caller's MCP connections.",{"application\u002Fjson":1237},{"schema":1238},{"type":117,"properties":1239,"required":1245},{"mcp_url":1240,"can_write":1242,"connections":1243},{"type":225,"example":1241},"https:\u002F\u002Fmcp.pealcast.io\u002Fmcp",{"type":318},{"type":120,"items":1244},{"$ref":1142},[1246,1247,1248],"mcp_url","can_write","connections",{"$ref":97},{"$ref":1251},"#\u002Fcomponents\u002Fresponses\u002FSessionRequired",{"operationId":1253,"summary":1254,"x-internal":6,"description":1255,"tags":1256,"parameters":1257,"requestBody":1259,"responses":1268},"createMcpConnection","Connect my AI assistant (mint a personal MCP token)","**Internal, session-only.** Mints the caller's personal MCP token and returns it **once**.\nWrite access is capped by the caller's role (only owner\u002Fadmin may create a write-capable\nconnection). Requires a human login.\n",[52],[1258],{"$ref":84},{"required":620,"content":1260},{"application\u002Fjson":1261},{"schema":1262},{"type":117,"properties":1263},{"name":1264,"access":1266},{"type":225,"default":1265},"My AI assistant",{"type":225,"enum":1267,"default":1163},[1163,1164],{"200":1269,"401":1282,"403":1283,"422":1284},{"description":1270,"content":1271},"The new connection (plaintext token returned once).",{"application\u002Fjson":1272},{"schema":1273},{"type":117,"properties":1274,"required":1280},{"ok":1275,"token":1276,"mcp_url":1278,"connection":1279},{"type":318,"const":6},{"type":225,"description":1277},"The plaintext MCP token — shown only in this response.",{"type":225},{"$ref":1142},[464,787,1246,1281],"connection",{"$ref":97},{"$ref":1251},{"description":1285,"content":1286},"Could not connect (`INVALID`).",{"application\u002Fjson":1287},{"schema":1288},{"$ref":158},{"operationId":1290,"summary":1291,"x-internal":6,"description":1292,"tags":1293,"parameters":1294,"responses":1299},"revokeMcpConnection","Disconnect an AI connection","**Internal, session-only.** Revokes a personal MCP connection by id. A user may revoke their\nown; an owner\u002Fadmin may revoke anyone's (offboarding). Requires a human login.\n",[52],[1295,1296],{"$ref":84},{"name":139,"in":140,"required":6,"description":1297,"schema":1298},"The connection id.",{"type":143},{"200":1300,"400":1309,"401":1313,"403":1314,"404":1319},{"description":1301,"content":1302},"The connection was revoked.",{"application\u002Fjson":1303},{"schema":1304},{"type":117,"properties":1305,"required":1308},{"ok":1306,"connection":1307},{"type":318,"const":6},{"$ref":1142},[464],{"description":1214,"content":1310},{"application\u002Fjson":1311},{"schema":1312},{"$ref":158},{"$ref":97},{"description":1315,"content":1316},"Not your connection, or not signed in (`FORBIDDEN` \u002F `SESSION_REQUIRED`).",{"application\u002Fjson":1317},{"schema":1318},{"$ref":158},{"description":1320,"content":1321},"No such connection (`NO_TOKEN`).",{"application\u002Fjson":1322},{"schema":1323},{"$ref":158},{"get":1325},{"operationId":1326,"summary":1327,"x-internal":6,"description":1328,"tags":1329,"parameters":1330,"responses":1332},"getAppBootstrap","Bootstrap the church-admin app","**Internal.** The single call the church-admin app makes on load — the signed-in user, all\nof their churches (the switcher), the resolved acting org + plan\u002Fpaywall state, and that\norg's channels (which drive post-login routing).\n",[40],[1331],{"$ref":84},{"200":1333,"401":1350,"403":1351},{"description":1334,"content":1335},"Everything the app shell needs to route + render.",{"application\u002Fjson":1336},{"schema":1337},{"type":117,"properties":1338,"required":1346},{"user":1339,"orgs":1340,"current_org":1342,"account":1343,"channels":1344},{"type":117,"additionalProperties":6},{"type":120,"items":1341},{"$ref":596},{"$ref":596},{"$ref":373},{"type":120,"items":1345},{"$ref":207},[1347,1348,1349,209],"user","orgs","current_org",{"$ref":97},{"description":1352,"content":1353},"No church is linked to this account yet (`NO_ORG`).",{"application\u002Fjson":1354},{"schema":1355},{"$ref":158},{"get":1357,"post":1373},{"operationId":1358,"summary":1359,"x-internal":6,"description":1360,"tags":1361,"responses":1362},"listMyOrgs","List the churches I can act in","**Internal.** The org-switcher list — every church the signed-in user has a role in, each\nflagged with whether it is the currently active one.\n",[40],{"200":1363,"401":1372},{"description":1364,"content":1365},"The user's churches.",{"application\u002Fjson":1366},{"schema":1367},{"type":117,"properties":1368,"required":1371},{"orgs":1369},{"type":120,"items":1370},{"$ref":596},[1348],{"$ref":97},{"operationId":1374,"summary":1375,"x-internal":6,"description":1376,"tags":1377,"requestBody":1378,"responses":1386},"createOrg","Add another church","**Internal.** Create a NEW church for the signed-in user and make them its owner\n(a 14-day, card-free trial of the Large plan), then re-scope this session to it so\nthe app lands in the new church. This is how an existing user adds a second (third, …)\norg; the marketing signup flow routes a known mobile\u002Femail here via sign-in rather than\nduplicating the account. A `pc_` API token is pinned to one org and may NOT create tenants.\n",[40],{"required":6,"content":1379},{"application\u002Fjson":1380},{"schema":1381},{"type":117,"properties":1382,"required":1385},{"name":1383},{"type":225,"description":1384},"The new church's name.",[234],{"201":1387,"400":1396,"401":1398,"403":1399},{"description":1388,"content":1389},"The new church, now the session's active org.",{"application\u002Fjson":1390},{"schema":1391},{"type":117,"properties":1392,"required":1395},{"ok":1393,"current_org":1394},{"type":318},{"$ref":596},[464,1349],{"description":1397},"Missing church name.",{"$ref":97},{"description":1400},"A read-only or org-pinned API token may not create a church.",{"get":1402,"delete":1450},{"operationId":1403,"summary":1404,"x-internal":6,"description":1405,"tags":1406,"responses":1407},"listMySessions","List my active login sessions (devices)","**Internal.** Every active (non-revoked, unexpired) login session for the signed-in user —\ndevice, browser, OS, readable IP, best-effort location, when it signed in and was last seen —\nwith the current session flagged. Backs the \"Where you're signed in\" security view.\n",[40],{"200":1408,"401":1449},{"description":1409,"content":1410},"The user's active sessions.",{"application\u002Fjson":1411},{"schema":1412},{"type":117,"properties":1413,"required":1447},{"sessions":1414},{"type":120,"items":1415},{"type":117,"properties":1416},{"id":1417,"actor":1418,"device":1420,"browser":1423,"os":1425,"device_type":1427,"ip":1434,"location":1436,"last_seen_at":1439,"signed_in_at":1441,"expires_at":1443,"current":1445,"active":1446},{"type":143},{"type":225,"enum":1419},[719,720,721],{"type":1421,"example":1422},[225,323],"Chrome on macOS",{"type":1424},[225,323],{"type":1426},[225,323],{"type":1428,"enum":1429},[225,323],[738,1430,1431,1432,1433],"tablet","desktop","bot",null,{"type":1435},[225,323],{"type":1437,"example":1438},[225,323],"San Diego, California, US",{"type":1440,"format":327},[225,323],{"type":1442,"format":327},[225,323],{"type":1444,"format":327},[225,323],{"type":318},{"type":318},[1448],"sessions",{"$ref":97},{"operationId":1451,"summary":1452,"x-internal":6,"description":1453,"tags":1454,"parameters":1455,"responses":1459},"revokeMySession","Revoke one of my sessions (remote sign-out)","**Internal.** Revokes a single session by id (must belong to the caller). Its token stops\nworking on the very next request — signing that device out everywhere.\n",[40],[1456],{"name":139,"in":140,"required":6,"description":1457,"schema":1458},"The session id to revoke.",{"type":225},{"200":1460,"400":1469,"401":1473,"404":1474},{"description":1461,"content":1462},"Revoked.",{"application\u002Fjson":1463},{"schema":1464},{"type":117,"properties":1465,"required":1468},{"ok":1466,"revoked":1467},{"type":318,"const":6},{"type":143},[464],{"description":1214,"content":1470},{"application\u002Fjson":1471},{"schema":1472},{"$ref":158},{"$ref":97},{"description":1475,"content":1476},"Not found (`NO_SESSION`).",{"application\u002Fjson":1477},{"schema":1478},{"$ref":158},{"post":1480},{"operationId":1481,"summary":1482,"x-internal":6,"description":1483,"tags":1484,"responses":1485},"revokeMyOtherSessions","Sign out all my other sessions","**Internal.** Revokes every OTHER active session for the signed-in user, keeping the current\none. Returns how many were revoked.\n",[40],{"200":1486,"401":1496},{"description":1487,"content":1488},"Other sessions revoked.",{"application\u002Fjson":1489},{"schema":1490},{"type":117,"properties":1491,"required":1494},{"ok":1492,"revoked_count":1493},{"type":318,"const":6},{"type":143},[464,1495],"revoked_count",{"$ref":97},{"post":1498},{"operationId":1499,"summary":1500,"x-internal":6,"description":1501,"tags":1502,"responses":1503},"stopImpersonation","End the current admin impersonation","**Internal.** The \"Exit\" action in the impersonation banner. Revokes the CURRENT session\nonly if it is an admin impersonation (its `impersonator_user_id` is set); the staff member's\nown admin session is untouched. The app then clears its token and returns to the admin panel.\n",[40],{"200":1504,"400":1512,"401":1517},{"description":1505,"content":1506},"Impersonation ended.",{"application\u002Fjson":1507},{"schema":1508},{"type":117,"properties":1509,"required":1511},{"ok":1510},{"type":318,"const":6},[464],{"description":1513,"content":1514},"Not an impersonation session (`NOT_IMPERSONATION`).",{"application\u002Fjson":1515},{"schema":1516},{"$ref":158},{"$ref":97},{"post":1519},{"operationId":1520,"summary":1521,"x-internal":6,"description":1522,"tags":1523,"requestBody":1524,"responses":1532},"switchOrg","Switch the active church","**Internal.** Re-scopes the live session to another of the user's churches\n(`auth_sessions.organization_id`), so auth itself is org-bound.\n",[40],{"required":6,"content":1525},{"application\u002Fjson":1526},{"schema":1527},{"type":117,"properties":1528,"required":1531},{"org":1529},{"type":225,"description":1530},"The target church id or slug.",[698],{"200":1533,"401":1542,"403":1543},{"description":1534,"content":1535},"The session was re-scoped.",{"application\u002Fjson":1536},{"schema":1537},{"type":117,"properties":1538,"required":1541},{"ok":1539,"current_org":1540},{"type":318,"const":6},{"$ref":596},[464,1349],{"$ref":97},{"description":1544,"content":1545},"No access to that church (`FORBIDDEN_ORG`).",{"application\u002Fjson":1546},{"schema":1547},{"$ref":158},{"get":1549,"post":1571},{"operationId":1550,"summary":1551,"x-internal":6,"description":1552,"tags":1553,"parameters":1554,"responses":1556},"listOrgUsers","List the church's users","**Internal.** Who can sign into this church's app and their role\n(owner|admin|manager|member), plus the caller's own role.\n",[40],[1555],{"$ref":84},{"200":1557,"401":1569,"403":1570},{"description":1558,"content":1559},"The church's users.",{"application\u002Fjson":1560},{"schema":1561},{"type":117,"properties":1562,"required":1567},{"my_role":1563,"users":1565},{"type":1564},[225,323],{"type":120,"items":1566},{"type":117,"additionalProperties":6},[1568],"users",{"$ref":97},{"$ref":99},{"operationId":1572,"summary":1573,"x-internal":6,"description":1574,"tags":1575,"parameters":1576,"requestBody":1578,"responses":1591},"inviteOrgUser","Invite a user to the church","**Internal.** Creates (by mobile) or reactivates a user and grants them a role; they sign in\nwith phone+OTP (no password). Owner\u002Fadmin only.\n",[40],[1577],{"$ref":84},{"required":6,"content":1579},{"application\u002Fjson":1580},{"schema":1581},{"type":117,"properties":1582,"required":1590},{"phone":1583,"role":1584,"name":1589},{"type":225},{"type":225,"enum":1585,"default":1588},[1586,719,1587,1588],"owner","manager","member",{"type":225},[856],{"200":1592,"400":1607,"401":1612,"403":1613,"422":1618},{"description":1593,"content":1594},"The user was added\u002Fupdated.",{"application\u002Fjson":1595},{"schema":1596},{"type":117,"properties":1597,"required":1604},{"ok":1598,"user_id":1599,"role":1600,"mobile":1601,"name":1602},{"type":318,"const":6},{"type":143},{"type":225},{"type":225},{"type":1603},[225,323],[464,1605,1606],"user_id","role",{"description":1608,"content":1609},"Invalid mobile (`BAD_PHONE`).",{"application\u002Fjson":1610},{"schema":1611},{"$ref":158},{"$ref":97},{"description":1614,"content":1615},"Only owners\u002Fadmins can add users (`FORBIDDEN`).",{"application\u002Fjson":1616},{"schema":1617},{"$ref":158},{"description":1619,"content":1620},"Could not add the user (`INVALID`).",{"application\u002Fjson":1621},{"schema":1622},{"$ref":158},{"post":1624},{"operationId":1625,"summary":1626,"x-internal":6,"description":1627,"tags":1628,"parameters":1629,"requestBody":1631,"responses":1641},"updateOrgTheme","Set the church's brand","**Internal.** Sets the org brand (logo\u002Ficon\u002Fprimary\u002Faccent) used across the public player\nand landing surfaces. Colors are validated hex; URLs must be https. Owner\u002Fadmin only.\n",[34],[1630],{"$ref":84},{"required":6,"content":1632},{"application\u002Fjson":1633},{"schema":1634},{"type":117,"properties":1635},{"logo_url":1636,"icon_url":1637,"primary_color":1638,"accent_color":1640},{"type":225},{"type":225},{"type":225,"example":1639},"#12233b",{"type":225},{"200":1642,"400":1651,"401":1656,"403":1657},{"description":1643,"content":1644},"The updated org theme.",{"application\u002Fjson":1645},{"schema":1646},{"type":117,"properties":1647,"required":1650},{"ok":1648,"theme":1649},{"type":318,"const":6},{"type":117,"additionalProperties":6},[464,656],{"description":1652,"content":1653},"Bad color or URL (`BAD_COLOR` \u002F `BAD_URL`).",{"application\u002Fjson":1654},{"schema":1655},{"$ref":158},{"$ref":97},{"description":1658,"content":1659},"Only owners\u002Fadmins can change branding (`FORBIDDEN`).",{"application\u002Fjson":1660},{"schema":1661},{"$ref":158},{"get":1663,"post":1692},{"operationId":1664,"summary":1665,"x-internal":6,"description":1666,"tags":1667,"parameters":1668,"responses":1671},"getChannelTheme","Get a channel's theme and custom code","**Internal.** Returns the channel's effective theme (org brand + channel overrides) plus the\nAdvanced custom CSS\u002FHTML\u002FJS for that channel's player page.\n",[34],[1669,1670],{"$ref":84},{"$ref":86},{"200":1672,"401":1689,"403":1690,"404":1691},{"description":1673,"content":1674},"The channel theme + custom code.",{"application\u002Fjson":1675},{"schema":1676},{"type":117,"properties":1677,"required":1687},{"org_theme":1678,"channel_theme":1679,"effective":1680,"custom_css":1681,"custom_html":1683,"custom_js":1685},{"type":117,"additionalProperties":6},{"type":117,"additionalProperties":6},{"type":117,"additionalProperties":6},{"type":1682},[225,323],{"type":1684},[225,323],{"type":1686},[225,323],[1688],"effective",{"$ref":97},{"$ref":99},{"$ref":101},{"operationId":1693,"summary":1694,"x-internal":6,"description":1695,"tags":1696,"parameters":1697,"requestBody":1699,"responses":1710},"updateChannelTheme","Set a channel's theme and custom code","**Internal.** Sets a channel's theme overrides and the Advanced custom CSS\u002FHTML\u002FJS (stored\nverbatim, rendered only on that church's own channel page). Colors are validated hex; URLs\nmust be https. Owner\u002Fadmin only.\n",[34],[1698],{"$ref":84},{"required":6,"content":1700},{"application\u002Fjson":1701},{"schema":1702},{"type":117,"properties":1703,"required":1709},{"slug":1704,"theme":1705,"custom_css":1706,"custom_html":1707,"custom_js":1708},{"type":225},{"type":117,"additionalProperties":6},{"type":225},{"type":225},{"type":225},[549],{"200":1711,"400":1720,"401":1724,"403":1725,"404":1729},{"description":1712,"content":1713},"The updated effective theme.",{"application\u002Fjson":1714},{"schema":1715},{"type":117,"properties":1716,"required":1719},{"ok":1717,"effective":1718},{"type":318,"const":6},{"type":117,"additionalProperties":6},[464,1688],{"description":1652,"content":1721},{"application\u002Fjson":1722},{"schema":1723},{"$ref":158},{"$ref":97},{"description":1658,"content":1726},{"application\u002Fjson":1727},{"schema":1728},{"$ref":158},{"$ref":101},{"get":1731,"post":1755},{"operationId":1732,"summary":1733,"x-internal":6,"description":1734,"tags":1735,"parameters":1736,"responses":1739},"getChannelGate","Get a channel's capture-gate config","**Internal.** Returns the channel's effective gate (the public config the player renders),\nthe raw overrides, the enabled toggle, the free-watch seconds, and the defaults.\n",[55],[1737,1738],{"$ref":84},{"$ref":86},{"200":1740,"401":1752,"403":1753,"404":1754},{"description":1741,"content":1742},"The gate configuration.",{"application\u002Fjson":1743},{"schema":1744},{"type":117,"properties":1745,"required":1751},{"effective":1746,"gate_config":1747,"gate_enabled":1748,"gate_free_watch_seconds":1749,"defaults":1750},{"type":117,"additionalProperties":6},{"type":117,"additionalProperties":6},{"type":318},{"type":143},{"type":117,"additionalProperties":6},[1688,653,654],{"$ref":97},{"$ref":99},{"$ref":101},{"operationId":1756,"summary":1757,"x-internal":6,"description":1758,"tags":1759,"parameters":1760,"requestBody":1762,"responses":1773},"updateChannelGate","Set a channel's capture-gate config","**Internal.** Updates the gate copy, extra fields, styling, and free-vs-hard behavior (the\ngate always collects a verified mobile — the moat). Owner\u002Fadmin only.\n",[55],[1761],{"$ref":84},{"required":6,"content":1763},{"application\u002Fjson":1764},{"schema":1765},{"type":117,"properties":1766,"required":1772},{"slug":1767,"gate_config":1768,"gate_enabled":1769,"gate_free_watch_seconds":1770},{"type":225},{"type":117,"additionalProperties":6},{"type":318},{"type":143,"minimum":5,"maximum":1771},86400,[549],{"200":1774,"400":1783,"401":1788,"403":1789,"404":1794},{"description":1775,"content":1776},"The updated effective gate config.",{"application\u002Fjson":1777},{"schema":1778},{"type":117,"properties":1779,"required":1782},{"ok":1780,"effective":1781},{"type":318,"const":6},{"type":117,"additionalProperties":6},[464,1688],{"description":1784,"content":1785},"Bad config value (`BAD_COLOR` \u002F `BAD_SECONDS`).",{"application\u002Fjson":1786},{"schema":1787},{"$ref":158},{"$ref":97},{"description":1790,"content":1791},"Only owners\u002Fadmins can change the gate (`FORBIDDEN`).",{"application\u002Fjson":1792},{"schema":1793},{"$ref":158},{"$ref":101},{"post":1796},{"operationId":1797,"summary":1798,"x-internal":6,"description":1799,"tags":1800,"parameters":1801,"requestBody":1803,"responses":1810},"setCustomDomain","Set the church's custom player domain","**Internal.** Sets or clears the org's custom watch\u002Fnotification hostname (CNAME target).\nRequires a plan that includes a dedicated domain (`402 NOT_ENTITLED` otherwise).\n",[37],[1802],{"$ref":84},{"required":6,"content":1804},{"application\u002Fjson":1805},{"schema":1806},{"type":117,"properties":1807},{"hostname":1808},{"type":225,"description":1809,"example":335},"The hostname (empty clears it).",{"200":1811,"400":1822,"401":1827,"402":1828,"409":1833},{"description":1812,"content":1813},"The domain was set\u002Fcleared.",{"application\u002Fjson":1814},{"schema":1815},{"type":117,"properties":1816,"required":1821},{"ok":1817,"hostname":1818,"cname_target":1820},{"type":318,"const":6},{"type":1819},[225,323],{"type":225},[464],{"description":1823,"content":1824},"Invalid hostname (`BAD_HOST`).",{"application\u002Fjson":1825},{"schema":1826},{"$ref":158},{"$ref":97},{"description":1829,"content":1830},"The plan doesn't include a custom domain (`NOT_ENTITLED`).",{"application\u002Fjson":1831},{"schema":1832},{"$ref":158},{"description":1834,"content":1835},"That domain is already in use (`DOMAIN_TAKEN`).",{"application\u002Fjson":1836},{"schema":1837},{"$ref":158},{"get":1839},{"operationId":1840,"summary":1841,"x-internal":6,"description":1842,"tags":1843,"security":1844,"parameters":1845,"responses":1850},"getPcoAuthorizeUrl","Get the Planning Center authorize URL","**Internal.** Returns the PCO authorize URL (plus an opaque anti-CSRF `state`) for the app\nto redirect the browser to. Kept on the backend so client_id + redirect_uri come from one\nplace. Called by the signed-in app SPA; the API URL never enters the browser.\n",[58],[],[1846],{"name":1847,"in":140,"required":620,"description":1848,"schema":1849},"state","An opaque anti-CSRF value; generated if omitted.",{"type":225},{"200":1851,"503":1861},{"description":1852,"content":1853},"The authorize URL + state.",{"application\u002Fjson":1854},{"schema":1855},{"type":117,"properties":1856,"required":1859},{"url":1857,"state":1858},{"type":225},{"type":225},[1860,1847],"url",{"description":1862,"content":1863},"Planning Center isn't configured (`PCO_UNCONFIGURED`).",{"application\u002Fjson":1864},{"schema":1865},{"$ref":158},{"post":1867},{"operationId":1868,"summary":1869,"x-internal":6,"description":1870,"tags":1871,"security":1872,"requestBody":1873,"responses":1881},"exchangePcoCode","Exchange a Planning Center OAuth code","**Internal.** The app relays the single-use `code` it received on its `\u002Fpco` callback; we\nexchange it for tokens **server-side** (the client secret + tokens never reach the browser)\nand store them encrypted in the central `integrations` table.\n",[58],[],{"required":6,"content":1874},{"application\u002Fjson":1875},{"schema":1876},{"type":117,"properties":1877,"required":1880},{"code":1878,"state":1879},{"type":225},{"type":225},[771],{"200":1882,"400":1894,"502":1899},{"description":1883,"content":1884},"The tokens were exchanged and stored.",{"application\u002Fjson":1885},{"schema":1886},{"type":117,"properties":1887,"required":1893},{"ok":1888,"connected":1889,"persisted":1890,"scope":1891},{"type":318,"const":6},{"type":318},{"type":318},{"type":1892},[225,323],[464,320],{"description":1895,"content":1896},"Missing code (`BAD_REQUEST`).",{"application\u002Fjson":1897},{"schema":1898},{"$ref":158},{"description":1900,"content":1901},"The Planning Center exchange failed (`PCO_EXCHANGE_FAILED`).",{"application\u002Fjson":1902},{"schema":1903},{"$ref":158},{"post":1905},{"operationId":1906,"summary":1907,"x-internal":6,"description":1908,"tags":1909,"security":1910,"requestBody":1911,"responses":1915},"pcoWebhook","Planning Center inbound webhook","**Internal webhook — HMAC auth, no bearer token.** The single inbound endpoint PCO POSTs to\nwhen a person\u002Ffield\u002Fcontact changes in a connected church. Authenticity is proven by the\nper-subscription signature (`X-PCO-Webhooks-Authenticity`) over the raw body; the matching\nintegration IS the org. A verified delivery is always 200-acked (even if unmapped).\n",[61],[],{"required":6,"content":1912},{"application\u002Fjson":1913},{"schema":1914},{"type":117,"additionalProperties":6},{"200":1916,"403":1918},{"$ref":1917},"#\u002Fcomponents\u002Fresponses\u002FWebhookAck",{"$ref":1919},"#\u002Fcomponents\u002Fresponses\u002FBadSignature",{"post":1921},{"operationId":1922,"summary":1923,"x-internal":6,"description":1924,"tags":1925,"security":1926,"requestBody":1927,"responses":1931},"cloudflareStreamWebhook","Cloudflare Stream inbound webhook","**Internal webhook — HMAC auth, no bearer token.** PealCast's go-live detection: Cloudflare\nposts live-input state changes (connected\u002Fdisconnected\u002Ferrored) and recording readiness\nhere. We map `live_input → channel → org`, drive the broadcast lifecycle, and fan out\ngo-live notifications. A validly-signed event is always 200-acked; a bad signature is 401.\n",[61],[],{"required":6,"content":1928},{"application\u002Fjson":1929},{"schema":1930},{"type":117,"additionalProperties":6},{"200":1932,"401":1933},{"$ref":1917},{"$ref":1919},{"post":1935},{"operationId":1936,"summary":1937,"x-internal":6,"description":1938,"tags":1939,"security":1940,"requestBody":1941,"responses":1945},"signalwireSmsInbound","SignalWire inbound-SMS webhook","**Internal webhook — HMAC auth, no bearer token.** SignalWire posts here when someone texts\none of our numbers. We record the inbound message and honor STOP\u002FSTART keywords against our\naudience. Returns empty TwiML (SignalWire's Advanced Opt-Out owns the compliance replies).\n",[61],[],{"required":6,"content":1942},{"application\u002Fx-www-form-urlencoded":1943},{"schema":1944},{"type":117,"additionalProperties":6},{"200":1946,"401":1952},{"description":1947,"content":1948},"Empty TwiML acknowledgement.",{"text\u002Fxml":1949},{"schema":1950},{"type":225,"example":1951},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\u003CResponse>\u003C\u002FResponse>",{"$ref":1919},{"post":1954},{"operationId":1955,"summary":1956,"x-internal":6,"description":1957,"tags":1958,"security":1959,"requestBody":1960,"responses":1964},"signalwireSmsStatus","SignalWire SMS delivery-status webhook","**Internal webhook — HMAC auth, no bearer token.** SignalWire posts delivery updates for\nevery outbound SMS; we find the message by `MessageSid` and advance its status\n(forward-only) with the carrier error code, if any.\n",[61],[],{"required":6,"content":1961},{"application\u002Fx-www-form-urlencoded":1962},{"schema":1963},{"type":117,"additionalProperties":6},{"200":1965,"401":1966},{"$ref":1917},{"$ref":1919},{"post":1968},{"operationId":1969,"summary":1970,"x-internal":6,"description":1971,"tags":1972,"security":1973,"requestBody":1974,"responses":1979},"supportCallIncoming","Support line incoming call → greeting + voicemail record (LaML)","**Internal webhook — signature auth (SignalWire), no bearer token.** Fired by SignalWire\nwhen someone calls the toll-free support line. Returns LaML that plays the recorded welcome\ngreeting and then records a voicemail (recording is on for all calls). Authenticity is\nproven by SignalWire's request signature, never a bearer token.\n",[61],[],{"required":6,"description":1975,"content":1976},"SignalWire form-encoded voice webhook payload (`CallSid`, `From`, `To`, …).",{"application\u002Fx-www-form-urlencoded":1977},{"schema":1978},{"type":117,"additionalProperties":6},{"200":1980,"401":1985},{"description":1981,"content":1982},"LaML that greets the caller and records a voicemail.",{"text\u002Fxml":1983},{"schema":1984},{"type":225},{"$ref":1919},{"post":1987},{"operationId":1988,"summary":1989,"x-internal":6,"description":1990,"tags":1991,"security":1992,"requestBody":1993,"responses":1998},"supportCallRecording","Voicemail recording ready → relay to Slack","**Internal webhook — signature auth (SignalWire), no bearer token.** Fired when a support\nvoicemail recording is ready. We relay the recording (URL + SID + duration) into the Slack\nsupport thread for that caller. Authenticity is proven by SignalWire's request signature.\n",[61],[],{"required":6,"description":1994,"content":1995},"SignalWire form-encoded recording callback (`RecordingUrl`, `RecordingSid`, `RecordingDuration`, `CallSid`, …).",{"application\u002Fx-www-form-urlencoded":1996},{"schema":1997},{"type":117,"additionalProperties":6},{"200":1999,"401":2000},{"$ref":1917},{"$ref":1919},{"post":2002},{"operationId":2003,"summary":2004,"x-internal":6,"description":2005,"tags":2006,"security":2007,"requestBody":2008,"responses":2013},"supportCallTranscription","Voicemail transcription callback → post transcript to Slack","**Internal webhook — signature auth (SignalWire), no bearer token.** Fired when the\nvoicemail transcription is ready. We post the transcript text into the caller's Slack\nsupport thread. Authenticity is proven by SignalWire's request signature.\n",[61],[],{"required":6,"description":2009,"content":2010},"SignalWire form-encoded transcription callback (`TranscriptionText`, `TranscriptionStatus`, `CallSid`, …).",{"application\u002Fx-www-form-urlencoded":2011},{"schema":2012},{"type":117,"additionalProperties":6},{"200":2014,"401":2015},{"$ref":1917},{"$ref":1919},{"post":2017},{"operationId":2018,"summary":2019,"x-internal":6,"description":2020,"tags":2021,"security":2022,"requestBody":2023,"responses":2028},"supportCallStatus","Support call status callback (lifecycle\u002Fduration)","**Internal webhook — signature auth (SignalWire), no bearer token.** Fired as a support\ncall moves through its lifecycle (ringing\u002Fanswered\u002Fcompleted) with the final call duration.\nWe advance the logged `support_calls` row. Authenticity is proven by SignalWire's request\nsignature.\n",[61],[],{"required":6,"description":2024,"content":2025},"SignalWire form-encoded status callback (`CallSid`, `CallStatus`, `CallDuration`, …).",{"application\u002Fx-www-form-urlencoded":2026},{"schema":2027},{"type":117,"additionalProperties":6},{"200":2029,"401":2030},{"$ref":1917},{"$ref":1919},{"post":2032},{"operationId":2033,"summary":2034,"x-internal":6,"description":2035,"tags":2036,"security":2037,"requestBody":2038,"responses":2043},"supportCallFail","Voice fallback URL → apologetic LaML","**Internal webhook — signature auth (SignalWire), no bearer token.** The voice fallback URL\nSignalWire calls when the primary voice webhook errors. Returns an apologetic LaML message\nso the caller is never left with dead air. Authenticity is proven by SignalWire's request\nsignature.\n",[61],[],{"required":6,"description":2039,"content":2040},"SignalWire form-encoded voice fallback payload (`CallSid`, `ErrorCode`, …).",{"application\u002Fx-www-form-urlencoded":2041},{"schema":2042},{"type":117,"additionalProperties":6},{"200":2044,"401":2049},{"description":2045,"content":2046},"Apologetic LaML message.",{"text\u002Fxml":2047},{"schema":2048},{"type":225},{"$ref":1919},{"post":2051},{"operationId":2052,"summary":2053,"x-internal":6,"description":2054,"tags":2055,"security":2056,"requestBody":2057,"responses":2062},"supportSmsInbound","Inbound text\u002FMMS to the support line → post to the Slack thread","**Internal webhook — signature auth (SignalWire), no bearer token.** Fired when someone\ntexts (or MMS's) the toll-free support line. We post the message into the caller's Slack\nsupport thread (one thread per external number), relaying any media (images\u002FPDF\u002Ftext\u002FHTML)\nas Slack file uploads and enriching with the sender's PealCast identity. Authenticity is\nproven by SignalWire's request signature.\n",[61],[],{"required":6,"description":2058,"content":2059},"SignalWire form-encoded inbound SMS\u002FMMS payload (`From`, `To`, `Body`, `NumMedia`, `MediaUrl{n}`, …).",{"application\u002Fx-www-form-urlencoded":2060},{"schema":2061},{"type":117,"additionalProperties":6},{"200":2063,"401":2067},{"description":1947,"content":2064},{"text\u002Fxml":2065},{"schema":2066},{"type":225,"example":1951},{"$ref":1919},{"post":2069},{"operationId":2070,"summary":2071,"x-internal":6,"description":2072,"tags":2073,"security":2074,"requestBody":2075,"responses":2080},"slackEvents","Slack Events API → staff reply in a support thread is texted back","**Internal webhook — signature auth (Slack), no bearer token.** The Slack Events API\nendpoint. When a staff member replies in a support thread, we text that reply back to the\nperson on the support line (unicode emoji both ways). Also handles Slack's `url_verification`\nhandshake. Authenticity is proven by the Slack request signature\n(`X-Slack-Signature` \u002F `X-Slack-Request-Timestamp`), never a bearer token.\n",[61],[],{"required":6,"description":2076,"content":2077},"Slack Events API JSON payload (`url_verification` challenge or an `event_callback` with a `message` event).",{"application\u002Fjson":2078},{"schema":2079},{"type":117,"additionalProperties":6},{"200":2081,"401":2088},{"description":2082,"content":2083},"Acknowledged (or the `url_verification` challenge echoed back).",{"application\u002Fjson":2084,"text\u002Fplain":2086},{"schema":2085},{"type":117,"additionalProperties":6},{"schema":2087},{"type":225},{"$ref":1919},{"post":2090},{"operationId":2091,"summary":2092,"x-internal":6,"description":2093,"tags":2094,"security":2095,"requestBody":2096,"responses":2101},"stripeWebhook","Stripe events → posted to the","**Internal webhook — signature auth (Stripe), no bearer token.** Stripe POSTs account\nevents here (new customers, payments succeeded\u002Ffailed, refunds, disputes, payouts); we\nverify the `Stripe-Signature` HMAC over the raw body against the stage's `whsec_…`\n(SSM `\u002Fpealcast\u002F{env}\u002Fstripe\u002Fwebhook_secret`) and relay a formatted line to the\n`#pealcast-{stage}-stripe` Slack channel. dev = Stripe test mode, prod = live.\n",[61],[],{"required":6,"description":2097,"content":2098},"A Stripe Event object (JSON).",{"application\u002Fjson":2099},{"schema":2100},{"type":117,"additionalProperties":6},{"200":2102,"400":2104},{"description":2103},"Received.",{"$ref":1919},{"post":2106},{"operationId":2107,"summary":2108,"x-internal":6,"description":2109,"tags":2110,"security":2111,"requestBody":2112,"responses":2124},"verifyWatcherGate","Verify the watcher capture gate","**Internal, no auth.** After an anonymous watcher exceeds a channel's free-watch allowance,\nthey submit their OTP code + optional prayer request here. We verify the code, upsert the\nsubscriber (the owned audience, deduped by verified mobile), record the prayer submission,\nand issue a watcher session so playback resumes. Identify once → pre-authed forever.\n",[55],[],{"required":6,"content":2113},{"application\u002Fjson":2114},{"schema":2115},{"type":117,"properties":2116,"required":2123},{"channel":2117,"identifier":2118,"phone":2120,"code":2121,"prayer_request":2122},{"type":225,"description":616},{"type":225,"description":2119},"The verified mobile (or email). Alias `phone` accepted.",{"type":225},{"type":225},{"type":225},[560,725,771],{"200":2125,"400":2134,"401":2139,"404":2143},{"description":2126,"content":2127},"The watcher was identified; a session token is returned.",{"application\u002Fjson":2128},{"schema":2129},{"type":117,"properties":2130,"required":2133},{"ok":2131,"token":2132},{"type":318,"const":6},{"type":225},[464,787],{"description":2135,"content":2136},"Invalid identifier or code (`BAD_IDENTIFIER`).",{"application\u002Fjson":2137},{"schema":2138},{"$ref":158},{"description":794,"content":2140},{"application\u002Fjson":2141},{"schema":2142},{"$ref":158},{"$ref":101},{"get":2145},{"operationId":2146,"summary":2147,"x-internal":6,"description":2148,"tags":2149,"security":2150,"parameters":2151,"responses":2158},"getSubscribeInfo","Get the notification sign-up widget config","**Internal, no auth.** What the public opt-in widget needs to render — the channel + org\nnames, theme, and the notification methods available for the channel.\n",[37],[],[2152,2154,2155],{"name":560,"in":140,"required":6,"description":616,"schema":2153},{"type":225},{"$ref":84},{"name":624,"in":140,"required":620,"description":2156,"schema":2157},"The watch host (resolves a custom domain).",{"type":225},{"200":2159,"404":2174},{"description":2160,"content":2161},"The widget config.",{"application\u002Fjson":2162},{"schema":2163},{"type":117,"properties":2164,"required":2171},{"channel_name":2165,"org_name":2166,"theme":2168,"methods":2169},{"type":225},{"type":2167},[225,323],{"type":117,"additionalProperties":6},{"type":120,"items":2170},{"type":117,"additionalProperties":6},[2172,2173],"channel_name","methods",{"$ref":101},{"post":2176},{"operationId":2177,"summary":2178,"x-internal":6,"description":2179,"tags":2180,"security":2181,"requestBody":2182,"responses":2198},"startSubscribe","Start notification opt-in (send an OTP)","**Internal, no auth.** Validates the chosen methods (a real, textable mobile is ALWAYS\nrequired; an email when an email method is chosen) and texts an OTP to confirm.\n",[37],[],{"required":6,"content":2183},{"application\u002Fjson":2184},{"schema":2185},{"type":117,"properties":2186,"required":2197},{"channel":2187,"org":2188,"host":2189,"methods":2190,"phone":2194,"email":2195,"name":2196},{"type":225},{"type":225},{"type":225},{"type":120,"items":2191,"example":2192},{"type":225},[344,2193],"native.email",{"type":225},{"type":225},{"type":225},[560,2173,856],{"200":2199,"400":2208,"404":2213,"429":2214},{"description":2200,"content":2201},"The OTP was sent.",{"application\u002Fjson":2202},{"schema":2203},{"type":117,"properties":2204,"required":2207},{"ok":2205,"dev_code":2206},{"type":318,"const":6},{"type":225,"description":867},[464],{"description":2209,"content":2210},"No method, non-mobile, or missing\u002Fundeliverable email.",{"application\u002Fjson":2211},{"schema":2212},{"$ref":158},{"$ref":101},{"description":875,"content":2215},{"application\u002Fjson":2216},{"schema":2217},{"$ref":158},{"post":2219},{"operationId":2220,"summary":2221,"x-internal":6,"description":2222,"tags":2223,"security":2224,"requestBody":2225,"responses":2240},"verifySubscribe","Confirm notification opt-in","**Internal, no auth.** Verifies the OTP, upserts the subscriber (owned audience, deduped by\nverified mobile), and creates one subscription per chosen, channel-available method.\n",[37],[],{"required":6,"content":2226},{"application\u002Fjson":2227},{"schema":2228},{"type":117,"properties":2229,"required":2239},{"channel":2230,"org":2231,"host":2232,"methods":2233,"phone":2235,"email":2236,"name":2237,"code":2238},{"type":225},{"type":225},{"type":225},{"type":120,"items":2234},{"type":225},{"type":225},{"type":225},{"type":225},{"type":225},[560,2173,856,771],{"200":2241,"400":2252,"401":2257,"404":2262},{"description":2242,"content":2243},"The subscriptions were created.",{"application\u002Fjson":2244},{"schema":2245},{"type":117,"properties":2246,"required":2250},{"ok":2247,"subscribed":2248},{"type":318,"const":6},{"type":143,"description":2249},"How many method subscriptions were created.",[464,2251],"subscribed",{"description":2253,"content":2254},"No method or bad phone (`NO_METHOD` \u002F `BAD_PHONE`).",{"application\u002Fjson":2255},{"schema":2256},{"$ref":158},{"description":2258,"content":2259},"Wrong code (`BAD_CODE`).",{"application\u002Fjson":2260},{"schema":2261},{"$ref":158},{"$ref":101},{"post":2264},{"operationId":2265,"summary":2266,"x-internal":6,"description":2267,"tags":2268,"security":2269,"requestBody":2270,"responses":2287},"startWatchSession","Start a watch session","**Internal, no auth.** Called once when the player opens a channel. Mints a unique Cloudflare\nStream signed playback token for this session, stores the `watch_sessions` row, and returns\nthe token, live HLS URL, and the channel's gate config.\n",[43],[],{"required":6,"content":2271},{"application\u002Fjson":2272},{"schema":2273},{"type":117,"properties":2274,"required":2285},{"session_uid":2275,"channel":2276,"c":2278,"org":2279,"host":2280,"broadcast_id":2281,"token":2282,"gate_state":2284},{"type":225},{"type":225,"description":2277},"The channel slug\u002Fid. Alias `c` accepted.",{"type":225},{"type":225},{"type":225},{"type":143},{"type":225,"description":2283},"An optional signed watch link that pre-identifies the viewer.",{"type":225},[2286,560],"session_uid",{"200":2288,"400":2304,"404":2309},{"description":2289,"content":2290},"The session was started.",{"application\u002Fjson":2291},{"schema":2292},{"type":117,"properties":2293,"required":2303},{"ok":2294,"is_live":2295,"broadcast_id":2296,"hls_url":2298,"cf_token":2300,"gate":2302},{"type":318,"const":6},{"type":318},{"type":2297},[143,323],{"type":2299},[225,323],{"type":2301},[225,323],{"type":117,"additionalProperties":6},[464,652],{"description":2305,"content":2306},"Missing session_uid (`BAD_REQUEST`).",{"application\u002Fjson":2307},{"schema":2308},{"$ref":158},{"$ref":101},{"post":2311},{"operationId":2312,"summary":2313,"x-internal":6,"description":2314,"tags":2315,"security":2316,"requestBody":2317,"responses":2335},"watchSessionHeartbeat","Send a watch-session heartbeat","**Internal, no auth.** The player beats here every ~12s (and once via sendBeacon on unload).\nUpserts the `watch_sessions` row from the engagement + QoE metrics and returns the\nserver-recomputed QoE score so the client can reconcile its overlay.\n",[43],[],{"required":6,"content":2318},{"application\u002Fjson":2319},{"schema":2320},{"type":117,"properties":2321,"additionalProperties":6,"required":2334},{"session_uid":2322,"token":2323,"channel":2324,"broadcast_id":2325,"platform":2326,"device":2327,"joined_at":2328,"ended":2329,"watched_seconds":2330,"buffering_seconds":2331,"rebuffer_count":2332,"startup_ms":2333},{"type":225},{"type":225},{"type":225},{"type":143},{"type":225},{"type":225},{"type":225},{"type":318},{"type":143},{"type":143},{"type":143},{"type":143},[2286],{"200":2336,"400":2353},{"description":2337,"content":2338},"The heartbeat was recorded (or graded, when the DB isn't wired in dev).",{"application\u002Fjson":2339},{"schema":2340},{"type":117,"properties":2341,"required":2351},{"ok":2342,"persisted":2343,"session_uid":2344,"qoe_score":2345,"qoe_grade":2347,"watched_seconds":2349,"ended":2350},{"type":318,"const":6},{"type":318},{"type":225},{"type":2346},[143,323],{"type":2348},[225,323],{"type":143},{"type":318},[464,2352],"persisted",{"description":2305,"content":2354},{"application\u002Fjson":2355},{"schema":2356},{"$ref":158},{"get":2358},{"operationId":2359,"summary":2360,"x-internal":6,"description":2361,"tags":2362,"security":2363,"responses":2364},"getWwwPlans","Get the public pricing plan catalog","**Internal, no auth.** The single source the marketing pricing page reads — the displayable\nplans (in card order), prices in cents. During the Founding-100 window that's the founding\nrows + Custom.\n",[67],[],{"200":2365},{"description":2366,"content":2367},"The displayable plans.",{"application\u002Fjson":2368},{"schema":2369},{"type":117,"properties":2370,"required":2373},{"plans":2371},{"type":120,"items":2372},{"$ref":376},[382],{"get":2375},{"operationId":2376,"summary":2377,"x-internal":6,"description":2378,"tags":2379,"security":2380,"responses":2381},"getFoundingStatus","Get the live Founding-100 seat count","**Internal, no auth.** The real remaining Founding-100 seat count for the marketing site\n(honest scarcity — never a fake\u002Fresetting countdown). `active` flips false once the 100\nseats are gone.\n",[67],[],{"200":2382},{"description":2383,"content":2384},"The founding-seat status.",{"application\u002Fjson":2385},{"schema":2386},{"type":117,"properties":2387,"required":2392},{"total":2388,"taken":2389,"remaining":2390,"active":2391},{"type":143,"example":4},{"type":143},{"type":143},{"type":318},[2393,2394,2395,2396],"total","taken","remaining","active",{"get":2398},{"operationId":2399,"summary":2400,"x-internal":6,"description":2401,"tags":2402,"security":2403,"responses":2404},"getOpenApiSpec","Get the OpenAPI spec as JSON","**Internal, no auth.** Serves this OpenAPI spec (the single source of truth) as JSON so the\nmarketing site's API-reference page renders it at build time.\n",[67],[],{"200":2405,"503":2410},{"description":2406,"content":2407},"The OpenAPI document as JSON.",{"application\u002Fjson":2408},{"schema":2409},{"type":117,"additionalProperties":6},{"description":2411,"content":2412},"The spec couldn't be read (`NO_SPEC`).",{"application\u002Fjson":2413},{"schema":2414},{"$ref":158},{"get":2416},{"operationId":2417,"summary":2418,"x-internal":6,"description":2419,"tags":2420,"responses":2421},"getAdminOverview","Get the platform-admin overview","**Internal, staff only.** Top-line platform counts + the signed-in staff profile.\nAuthenticates as `actor='admin'` exclusively — an `app` or `watcher` token is rejected 403.\n",[64],{"200":2422,"403":2436},{"description":2423,"content":2424},"The overview counts and staff profile.",{"application\u002Fjson":2425},{"schema":2426},{"type":117,"properties":2427,"required":2434},{"ok":2428,"counts":2429,"me":2433},{"type":318,"const":6},{"type":117,"properties":2430},{"organizations":2431,"subscribers":2432},{"type":143},{"type":143},{"type":117,"additionalProperties":6},[464,2435],"counts",{"description":2437,"content":2438},"Staff only (`NOT_ALLOWED`).",{"application\u002Fjson":2439},{"schema":2440},{"$ref":158},{"get":2442},{"operationId":2443,"summary":2444,"x-internal":6,"description":2445,"tags":2446,"responses":2447},"getAdminStats","Platform-wide usage stats","**Internal, staff only.** Top-line totals across all churches (accounts, channels, subscribers, watch minutes, active sessions).",[64],{"200":2448,"403":2453},{"description":2449,"content":2450},"Platform stats.",{"application\u002Fjson":2451},{"schema":2452},{"type":117,"additionalProperties":6},{"description":2437,"content":2454},{"application\u002Fjson":2455},{"schema":2456},{"$ref":158},{"get":2458},{"operationId":2459,"summary":2460,"x-internal":6,"description":2461,"tags":2462,"parameters":2463,"responses":2468},"listAdminOrgs","List \u002F search all churches","**Internal, staff only.** Every organization with summary counts (channels, subscribers, watch minutes, plan, status, owner). `q` filters by name \u002F slug \u002F domain.",[64],[2464],{"name":2465,"in":140,"required":620,"description":2466,"schema":2467},"q","Search term (name \u002F slug \u002F custom domain).",{"type":225},{"200":2469,"403":2479},{"description":2470,"content":2471},"The org list.",{"application\u002Fjson":2472},{"schema":2473},{"type":117,"properties":2474,"required":2478},{"orgs":2475,"count":2477},{"type":120,"items":2476},{"type":117,"additionalProperties":6},{"type":143},[1348],{"description":2437,"content":2480},{"application\u002Fjson":2481},{"schema":2482},{"$ref":158},{"get":2484},{"operationId":2485,"summary":2486,"x-internal":6,"description":2487,"tags":2488,"parameters":2489,"responses":2493},"getAdminOrg","One church — channels, members, usage, billing","**Internal, staff only.** Full drill-down for a single organization: summary, billing\u002Fpaywall state, usage, channels, members (with roles), and connected integrations.",[64],[2490],{"name":139,"in":140,"required":6,"description":2491,"schema":2492},"Organization id or slug.",{"type":225},{"200":2494,"403":2499,"404":2503},{"description":2495,"content":2496},"The org detail.",{"application\u002Fjson":2497},{"schema":2498},{"type":117,"additionalProperties":6},{"description":2437,"content":2500},{"application\u002Fjson":2501},{"schema":2502},{"$ref":158},{"description":2504,"content":2505},"Not found (`NO_ORG`).",{"application\u002Fjson":2506},{"schema":2507},{"$ref":158},{"post":2509},{"operationId":2510,"summary":2511,"x-internal":6,"description":2512,"tags":2513,"requestBody":2514,"responses":2525},"adminImpersonate","Open a church-app session AS one of its users","**Internal, staff only.** Mints a normal `app` session for the church, stamped with the\nadmin's `impersonator_user_id` (the AuthSession model VALIDATES the impersonator is platform\nstaff). Short-lived (1h) + fully revocable. Returns a hand-off `app_url` carrying the token in\nthe URL FRAGMENT — the admin UI opens it and the app's \u002Fimpersonate page enters the account.\n",[64],{"required":6,"content":2515},{"application\u002Fjson":2516},{"schema":2517},{"type":117,"required":2518,"properties":2520},[2519],"organization_id",{"organization_id":2521,"user_id":2522,"reason":2524},{"type":225},{"type":225,"description":2523},"Optional — defaults to the org owner.",{"type":225},{"200":2526,"403":2543,"404":2547},{"description":2527,"content":2528},"Impersonation session issued.",{"application\u002Fjson":2529},{"schema":2530},{"type":117,"properties":2531,"required":2541},{"ok":2532,"token":2533,"expires_at":2534,"app_url":2535,"acting_as":2537},{"type":318,"const":6},{"type":225},{"type":225,"format":327},{"type":225,"description":2536},"Hand-off URL; token in the fragment.",{"type":117,"properties":2538},{"org":2539,"user":2540},{"type":225},{"type":225},[464,787,2542],"app_url",{"description":2437,"content":2544},{"application\u002Fjson":2545},{"schema":2546},{"$ref":158},{"description":2548,"content":2549},"Org or target user not found (`NO_ORG` \u002F `NO_TARGET`).",{"application\u002Fjson":2550},{"schema":2551},{"$ref":158},{"post":2553,"get":2581},{"operationId":2554,"summary":2555,"x-internal":6,"description":2556,"tags":2557,"requestBody":2558,"responses":2567},"mcpJsonRpc","MCP JSON-RPC endpoint","**Internal.** The Model Context Protocol JSON-RPC 2.0 entry point (Streamable HTTP). A\nchurch points its own AI client at `mcp.pealcast.io` with a `pc_` token; each tool call runs\nagainst the same org-scoped model methods the dashboard uses. Implements `initialize`,\n`ping`, `tools\u002Flist`, `tools\u002Fcall`. A read-only token is blocked from write tools.\n",[73],{"required":6,"content":2559},{"application\u002Fjson":2560},{"schema":2561},{"description":2562,"oneOf":2563},"A single JSON-RPC 2.0 message or a batch array.",[2564,2565],{"type":117,"additionalProperties":6},{"type":120,"items":2566},{"type":117,"additionalProperties":6},{"200":2568,"202":2577,"401":2579},{"description":2569,"content":2570},"A JSON-RPC response (single object or batch array).",{"application\u002Fjson":2571},{"schema":2572},{"oneOf":2573},[2574,2575],{"type":117,"additionalProperties":6},{"type":120,"items":2576},{"type":117,"additionalProperties":6},{"description":2578},"Accepted — the reply to notification-only \u002F response-only posts (empty body).",{"description":2580},"Missing\u002Finvalid MCP token (JSON-RPC error, `WWW-Authenticate` challenge).",{"operationId":2582,"summary":2583,"x-internal":6,"description":2584,"tags":2585,"security":2586,"responses":2587},"mcpGet","MCP GET (not allowed)","**Internal.** No server-initiated SSE stream — the MCP spec allows 405 here. Use POST.\n",[73],[],{"405":2588},{"description":2589,"content":2590},"Method not allowed (`METHOD_NOT_ALLOWED`).",{"application\u002Fjson":2591},{"schema":2592},{"$ref":158},{"get":2594},{"operationId":2595,"summary":2596,"x-internal":6,"description":2597,"tags":2598,"security":2599,"responses":2600},"healthCheck","Health check","**Internal, no auth.** Used by CI deploy verification (asserts version == release) and\nuptime monitoring. Reports DB reachability without failing if the DB is down.\n",[70],[],{"200":2601},{"description":2602,"content":2603},"The service is healthy.",{"application\u002Fjson":2604},{"schema":2605},{"type":117,"properties":2606,"required":2615},{"status":2607,"environment":2609,"version":2610,"db":2612,"timestamp":2614},{"type":225,"example":2608},"healthy",{"type":225},{"type":225,"description":2611},"The deployed commit SHA.",{"type":318,"description":2613},"Whether the database is reachable.",{"type":225,"format":327},[2616],"status",{"securitySchemes":2618,"parameters":2623,"responses":2637,"schemas":2708},{"BearerAuth":2619},{"type":2620,"scheme":2621,"description":2622},"http","bearer","A PealCast API token (pc_live_… \u002F pc_test_…). Create one in the app dashboard under Developer \u002F API tokens. Every token is pinned to one church.",{"OrgParam":2624,"SlugRequiredParam":2627,"RangeParam":2630},{"name":698,"in":140,"required":620,"description":2625,"schema":2626},"The church (organization) id or slug. Optional — the token is already pinned to one church; supply this only to disambiguate.",{"type":225},{"name":549,"in":140,"required":6,"description":2628,"schema":2629},"The channel slug.",{"type":225},{"name":2631,"in":140,"required":620,"description":2632,"schema":2633},"range","The report window, in days.",{"type":143,"enum":2634,"default":546},[2635,546,180,2636],7,365,{"Unauthenticated":2638,"ForbiddenOrg":2646,"ReadOnlyToken":2654,"NoChannel":2662,"NoOrg":2670,"SessionRequired":2678,"SessionOrRole":2686,"WebhookAck":2692,"BadSignature":2700},{"description":2639,"content":2640},"Missing or invalid token (`UNAUTHENTICATED`).",{"application\u002Fjson":2641},{"schema":2642,"example":2643},{"$ref":158},{"error":2644,"code":2645},"Authentication required","UNAUTHENTICATED",{"description":2647,"content":2648},"The token has no church, or is not permitted for this church (`NO_ORG` \u002F `FORBIDDEN_ORG` \u002F `FORBIDDEN`).",{"application\u002Fjson":2649},{"schema":2650,"example":2651},{"$ref":158},{"error":2652,"code":2653},"This token is not permitted for that church","FORBIDDEN_ORG",{"description":2655,"content":2656},"A read-only token attempted a write (`READ_ONLY_TOKEN`); may also be `FORBIDDEN_ORG`\u002F`FORBIDDEN`.",{"application\u002Fjson":2657},{"schema":2658,"example":2659},{"$ref":158},{"error":2660,"code":2661},"This token is read-only","READ_ONLY_TOKEN",{"description":2663,"content":2664},"No such channel for this church (`NO_CHANNEL`).",{"application\u002Fjson":2665},{"schema":2666,"example":2667},{"$ref":158},{"error":2668,"code":2669},"Channel not found","NO_CHANNEL",{"description":2671,"content":2672},"No such church (`NO_ORG`).",{"application\u002Fjson":2673},{"schema":2674,"example":2675},{"$ref":158},{"error":2676,"code":2677},"Church not found","NO_ORG",{"description":2679,"content":2680},"A `pc_` token attempted a credential-management action that requires a human dashboard login (`SESSION_REQUIRED`).",{"application\u002Fjson":2681},{"schema":2682,"example":2683},{"$ref":158},{"error":2684,"code":2685},"Sign in to the dashboard to manage API access.","SESSION_REQUIRED",{"description":2687,"content":2688},"Not signed in with a human session, or the caller lacks the required role (`SESSION_REQUIRED` \u002F `FORBIDDEN`). Owner\u002Fadmin only.",{"application\u002Fjson":2689},{"schema":2690,"example":2691},{"$ref":158},{"error":2684,"code":2685},{"description":2693,"content":2694},"The event was accepted (always acked for a verified\u002Fvalid delivery).",{"application\u002Fjson":2695},{"schema":2696},{"type":117,"properties":2697,"required":2699},{"ok":2698},{"type":318,"const":6},[464],{"description":2701,"content":2702},"The webhook signature could not be verified (`BAD_SIGNATURE`).",{"application\u002Fjson":2703},{"schema":2704,"example":2705},{"$ref":158},{"error":2706,"code":2707},"bad signature","BAD_SIGNATURE",{"Error":2709,"AccountState":2718,"Plan":2751,"ChannelNav":2813,"ChannelApp":2823,"StreamInfo":2849,"ViewerRow":2871,"Subscriber":2887,"OrgApp":2913,"Analytics":2925,"ViewerDetail":2958,"ArchiveRecording":3020,"NotificationsOverview":3045,"ReportSettings":3099,"ApiTokenInfo":3114,"SignupResult":3137},{"type":117,"description":2710,"properties":2711,"required":2716},"The standard error envelope returned for every 4xx\u002F402\u002F500.",{"error":2712,"code":2714},{"type":225,"description":2713,"example":2668},"A human-readable message.",{"type":225,"description":2715,"example":2669},"A stable machine code you can branch on.",[2717,771],"error",{"type":117,"description":2719,"properties":2720,"required":2748},"The church's current billing\u002Ftrial state.",{"state":2721,"status":2728,"trial_ends_at":2731,"trial_days_left":2733,"has_payment_method":2735,"is_founding_member":2737,"plan":2739,"next_bill_at":2746},{"type":225,"enum":2722,"description":2727},[2396,2723,2724,2725,2726],"past_due","trialing","trial_expired","locked","The high-level account state.",{"type":225,"description":2729,"example":2730},"A human-readable status label.","Trial — 9 days left",{"type":2732,"format":327},[225,323],{"type":143,"example":2734},9,{"type":318,"description":2736},"Whether a card is on file at Stripe.",{"type":318,"description":2738},"Whether the church is on a Founding 100 (50%\u002Flife) plan.",{"type":2740,"description":2741,"properties":2742},[117,323],"The church's current plan (null while unassigned).",{"code":2743,"name":2744},{"type":225,"example":1013},{"type":225,"example":2745},"Medium",{"type":2747,"format":327},[225,323],[1847,2616,2749,1076,2750],"trial_days_left","is_founding_member",{"type":117,"description":2752,"properties":2753,"required":2809},"A purchasable plan row (the plans table is the single source of truth for pricing).",{"code":2754,"name":2755,"tagline":2756,"founding":2758,"is_custom":2760,"price_cents":2762,"price_cents_annual":2765,"setup_fee_cents":2768,"trial_days":2771,"watchers":2773,"watchers_label":2775,"included_minutes":2777,"channels_included":2780,"overage_block_minutes":2784,"overage_block_cents":2787,"minutes_note":2790,"overage_note":2794,"archive_retention_days":2798,"archive_label":2800,"support_tier":2804,"dedicated_sms":2806,"dedicated_email":2807,"dedicated_domain":2808},{"type":225,"example":1013},{"type":225,"example":2745},{"type":225,"example":2757},"For a growing online congregation",{"type":318,"description":2759},"Whether this is a Founding 100 plan row.",{"type":318,"description":2761},"Whether this is the Custom (contact-us) tier.",{"type":143,"description":2763,"example":2764},"Monthly price, in cents.",9900,{"type":143,"description":2766,"example":2767},"Annual price, in cents (2 months free).",99000,{"type":143,"description":2769,"example":2770},"One-time setup\u002Fteam-training fee, in cents.",4900,{"type":143,"example":2772},14,{"type":143,"description":2774,"example":546},"Included active watchers (the customer-facing value metric).",{"type":225,"example":2776},"~30 watchers",{"type":143,"description":2778,"example":2779},"Included streamed minutes per month, pooled org-wide.",8000,{"type":2781,"description":2782,"example":2783},[143,323],"Included channels. Null means unlimited.",2,{"type":143,"description":2785,"example":2786},"Minutes granted per flat $20 overage block (also usable for archive). Ascends by tier — 250 \u002F 500 \u002F 750 on Small \u002F Medium \u002F Large.",500,{"type":143,"description":2788,"example":2789},"Price of one overage block, in cents.",2000,{"type":2791,"description":2792,"example":2793},[225,323],"DB-driven (i)-tooltip copy explaining the included-minutes math.","One watcher ≈ 60 minutes a week × 4 weeks = about 240 minutes a month, pooled across your whole church.",{"type":2795,"description":2796,"example":2797},[225,323],"DB-driven (i)-tooltip copy for the overage block.","$20 per 500 extra minutes or archive",{"type":143,"description":2799,"example":547},"Recording auto-delete window in days (30 \u002F 60 \u002F 90 on Small \u002F Medium \u002F Large), pushed to each channel's Cloudflare deleteRecordingAfterDays.",{"type":2801,"description":2802,"example":2803},[225,323],"Human storage-window label for the pricing card.","About 2 months",{"type":225,"example":2805},"email+phone",{"type":318},{"type":318},{"type":318},[771,234,2810,2811,2812],"price_cents","watchers","included_minutes",{"type":117,"description":2814,"properties":2815,"required":2822},"A lightweight channel entry for navigation lists.",{"slug":2816,"id":2817,"name":2818,"type":2819,"is_live":2821},{"type":225,"example":535},{"type":143},{"type":225,"example":227},{"type":225,"enum":2820},[230,231],{"type":318},[549,139,234,696,652],{"type":117,"description":2824,"properties":2825,"required":2847},"A channel's full configuration, including its encoder\u002Fingest stream info.",{"slug":2826,"id":2827,"name":2828,"type":2829,"description":2831,"timezone":2833,"is_live":2834,"gate_enabled":2835,"gate_free_watch_seconds":2836,"recording_retention_days":2837,"retention_min_days":2839,"retention_max_days":2841,"subscriber_count":2843,"created_at":2844,"stream_info":2845},{"type":225,"example":535},{"type":143},{"type":225},{"type":225,"enum":2830},[230,231],{"type":2832},[225,323],{"type":225,"example":409},{"type":318},{"type":318},{"type":143},{"type":143,"description":2838,"example":547},"The channel's recording auto-delete window (days), pushed to Cloudflare as deleteRecordingAfterDays. Editable within [retention_min_days, retention_max_days]; a change re-pushes to Cloudflare.",{"type":143,"description":2840,"example":546},"Minimum retention Cloudflare allows (30).",{"type":143,"description":2842,"example":180},"Maximum retention for the org's plan (the plan's archive window).",{"type":143},{"type":225,"format":327},{"$ref":2846},"#\u002Fcomponents\u002Fschemas\u002FStreamInfo",[549,139,234,696,652,2848],"stream_info",{"type":117,"description":2850,"properties":2851,"required":2868},"The channel's encoder\u002Fingest configuration — point your RTMP\u002FSRT encoder (hardware, OBS, or a simulcast destination) at these to go live. PealCast hosts the feed and is the sole watch destination.",{"cf_status":2852,"provisioned":2855,"rtmps_url":2857,"rtmps_stream_key":2859,"srt_url":2860,"srt_stream_key":2862,"hls_playback_url":2863,"sms_from_number":2865},{"type":225,"description":2853,"example":2854},"The Cloudflare Stream live-input status.","ready",{"type":318,"description":2856},"Whether the live input has been provisioned.",{"type":225,"example":2858},"rtmps:\u002F\u002Flive.cloudflare.com:443\u002Flive\u002F",{"type":225},{"type":225,"example":2861},"srt:\u002F\u002Flive.cloudflare.com:778",{"type":225},{"type":225,"description":2864},"The LL-HLS playback URL.",{"type":225,"description":2866,"example":2867},"The number go-live SMS notifications are sent from.","+15551234567",[2869,2870],"cf_status","provisioned",{"type":117,"description":2872,"properties":2873,"required":2885},"A single watcher (viewer) in your owned audience.",{"subscriber_id":2874,"name":2875,"mobile":2877,"first_watch_at":2879,"last_watch_at":2881,"watch_minutes":2883},{"type":143},{"type":225,"example":2876},"Jordan Rivera",{"type":225,"description":2878,"example":2867},"The viewer's verified mobile number.",{"type":2880,"format":327},[225,323],{"type":2882,"format":327},[225,323],{"type":143,"example":2884},128,[2886,234,738],"subscriber_id",{"type":117,"description":2888,"properties":2889,"required":2910},"An audience member (subscriber) — a real person in your owned, verified audience, deduped by verified mobile number.",{"id":2890,"first_name":2891,"last_name":2893,"full_name":2895,"mobile":2897,"mobile_verified":2900,"email":2901,"email_verified":2903,"source":2904,"created_at":2908},{"type":143},{"type":2892},[225,323],{"type":2894},[225,323],{"type":2896},[225,323],{"type":2898,"description":2899,"example":2867},[225,323],"The member's verified mobile number (the dedupe key; not editable).",{"type":318},{"type":2902},[225,323],{"type":318},{"type":2905,"description":2906,"example":2907},[225,323],"How this member first entered your audience.","watch_gate",{"type":2909,"format":327},[225,323],[139,2911,2912],"mobile_verified","email_verified",{"type":117,"description":2914,"properties":2915,"required":2924},"A church (organization) as seen by the church-facing app.",{"id":2916,"name":2917,"slug":2918,"role":2919,"status":2922,"timezone":2923},{"type":143},{"type":225},{"type":225},{"type":2920,"description":2921},[225,323],"The caller's role in this church (owner\u002Fadmin\u002Fmanager), when known.",{"type":225},{"type":225,"example":409},[139,234,549,2616],{"type":117,"description":2926,"properties":2927,"required":2953},"A channel's analytics and Shepherd's Report for the window. `new_watchers` are **New Faces** (recent first-time watchers to welcome); `drifting_watchers` are **Missed You** (regulars who have gone quiet and could use a check-in).",{"range_days":2928,"summary":2929,"new_watchers":2947,"drifting_watchers":2950},{"type":143,"example":546},{"type":117,"properties":2930,"required":2941},{"unique_viewers":2931,"anon_sessions":2933,"watch_minutes":2935,"avg_qoe":2936,"broadcasts":2939,"subscriber_count":2940},{"type":143,"description":2932},"Distinct identified watchers in the window.",{"type":143,"description":2934},"Watch sessions from not-yet-identified visitors.",{"type":143},{"type":2937,"description":2938},[143,323],"Average per-session quality-of-experience score (0–100).",{"type":143},{"type":143},[2942,2943,2944,2945,2946],"unique_viewers","anon_sessions","watch_minutes","broadcasts","subscriber_count",{"type":120,"description":2948,"items":2949},"New Faces — recent first-time watchers to welcome.",{"$ref":123},{"type":120,"description":2951,"items":2952},"Missed You — regulars who have gone quiet and could use a check-in.",{"$ref":123},[2954,2955,2956,2957],"range_days","summary","new_watchers","drifting_watchers",{"type":117,"description":2959,"properties":2960,"required":3016},"One watcher's complete profile, watch history, submissions, and notifications.",{"subscriber":2961,"totals":2973,"sessions":2980,"submissions":2996,"notifications":3004},{"type":117,"properties":2962,"required":2972},{"id":2963,"name":2964,"mobile":2965,"email":2966,"first_seen_at":2968,"last_seen_at":2970},{"type":143},{"type":225},{"type":225},{"type":2967},[225,323],{"type":2969,"format":327},[225,323],{"type":2971,"format":327},[225,323],[139,234,738],{"type":117,"properties":2974,"required":2979},{"sessions":2975,"watch_minutes":2976,"avg_qoe":2977},{"type":143},{"type":143},{"type":2978},[143,323],[1448,2944],{"type":120,"description":2981,"items":2982},"Every watch session for this viewer.",{"type":117,"properties":2983},{"broadcast_id":2984,"date":2985,"watch_minutes":2986,"completion":2988,"qoe":2991,"platform":2993},{"type":143},{"type":225,"format":327},{"type":2987},"number",{"type":2989,"description":2990},[2987,323],"Fraction of the broadcast watched (0–1).",{"type":2992},[143,323],{"type":2994,"example":2995},[225,323],"web",{"type":120,"description":2997,"items":2998},"Prayer\u002Fconnect form submissions from this viewer.",{"type":117,"properties":2999},{"type":3000,"fields":3002,"at":3003},{"type":225,"example":3001},"prayer",{"type":117,"additionalProperties":6},{"type":225,"format":327},{"type":120,"description":3005,"items":3006},"Go-live notifications sent to this viewer.",{"type":117,"properties":3007},{"integration_key":3008,"status":3009,"provider":3011,"sent_at":3014},{"type":225,"example":344},{"type":225,"example":3010},"sent",{"type":3012,"example":3013},[225,323],"signalwire",{"type":3015,"format":327},[225,323],[501,3017,1448,3018,3019],"totals","submissions","notifications",{"type":117,"description":3021,"properties":3022,"required":3041},"A recorded past broadcast (VOD) within the plan's retention window.",{"id":3023,"date":3024,"ended_at":3026,"duration_minutes":3028,"recording_status":3030,"cf_video_uid":3031,"playback_hls_url":3034,"watchers":3037,"watch_minutes":3038,"peak_concurrent":3039},{"type":143},{"type":225,"format":327,"description":3025},"When the broadcast started.",{"type":3027,"format":327},[225,323],{"type":2987,"example":3029},62.5,{"type":225,"example":2854},{"type":3032,"description":3033},[225,323],"The Cloudflare Stream video uid.",{"type":3035,"description":3036},[225,323],"The VOD HLS playback URL, or null if not yet available.",{"type":143},{"type":143},{"type":143,"description":3040},"Peak concurrent viewers during the broadcast.",[139,3042,3043,3044],"date","duration_minutes","recording_status",{"type":117,"description":3046,"properties":3047,"required":3095},"Who is subscribed to a channel's go-live notifications, and what has been sent.",{"totals":3048,"by_integration":3057,"people":3064,"recent":3074,"unsubscribes":3086},{"type":117,"properties":3049,"required":3053},{"subscribers":3050,"subscriptions":3051,"unsubscribes":3052},{"type":143},{"type":143},{"type":143},[3054,3055,3056],"subscribers","subscriptions","unsubscribes",{"type":120,"description":3058,"items":3059},"Subscription counts per delivery integration.",{"type":117,"properties":3060},{"key":3061,"label":3062,"count":3063},{"type":225,"example":344},{"type":225,"example":346},{"type":143},{"type":120,"description":3065,"items":3066},"The subscribed people and the integrations they've opted into.",{"type":117,"properties":3067},{"subscriber_id":3068,"name":3069,"mobile":3070,"integrations":3071},{"type":143},{"type":225},{"type":225},{"type":120,"items":3072,"example":3073},{"type":225},[344,2193],{"type":120,"description":3075,"items":3076},"Recently sent notifications.",{"type":117,"properties":3077},{"integration_key":3078,"status":3079,"provider":3080,"subscriber_id":3082,"sent_at":3083,"created_at":3085},{"type":225},{"type":225},{"type":3081},[225,323],{"type":143},{"type":3084,"format":327},[225,323],{"type":225,"format":327},{"type":120,"description":3087,"items":3088},"Recent unsubscribes.",{"type":117,"properties":3089},{"subscriber_id":3090,"name":3091,"mobile":3092,"integration_key":3093,"unsubscribed_at":3094},{"type":143},{"type":225},{"type":225},{"type":225},{"type":225,"format":327},[3017,3096,3097,3098,3056],"by_integration","people","recent",{"type":117,"description":3100,"properties":3101,"required":3109},"When the church's weekly Shepherd's Report is delivered.",{"enabled":3102,"wday":3103,"hour":3104,"timezone":3106,"last_sent_at":3107},{"type":318},{"type":143,"minimum":5,"maximum":430,"description":431},{"type":143,"minimum":5,"maximum":433,"description":3105},"Hour of day, local to `timezone`.",{"type":225,"example":409},{"type":3108,"format":327},[225,323],[3110,3111,3112,3113],"enabled","wday","hour","timezone",{"type":117,"description":3115,"properties":3116,"required":3136},"Metadata for a developer key or personal MCP connection. The plaintext secret is NEVER returned here — only the recognizable prefix + attributes. (Internal.)",{"id":3117,"name":3118,"token_type":3120,"access":3123,"prefix":3125,"last_used_at":3128,"expires_at":3130,"revoked_at":3132,"created_at":3134},{"type":143},{"type":3119},[225,323],{"type":225,"enum":3121},[698,3122],"personal",{"type":225,"enum":3124},[1163,1164],{"type":225,"description":3126,"example":3127},"The recognizable, non-secret token prefix.","pc_live_",{"type":3129,"format":327},[225,323],{"type":3131,"format":327},[225,323],{"type":3133,"format":327},[225,323],{"type":3135,"format":327},[225,323],[139],{"type":117,"description":3138,"properties":3139,"required":3144},"The result of a completed church-admin signup. (Internal.)",{"ok":3140,"new_account":3141,"profile":3143},{"type":318,"const":6},{"type":318,"description":3142},"Whether a brand-new account (and church) was created.",{"type":117,"additionalProperties":6},[464,3145,3146],"new_account","profile",1783815251877]