Preskoči na glavno vsebino

Integration & Extension Surface

Everything a third party can plug into runs through one API and one authorization model. This page is the map: it names every integration surface, shows how they connect, and links to the detailed reference for each. If you're building against B1, start here to pick the right door, then follow the link to the page that documents it in depth.

The Surfaces at a Glance

There are six ways in or out, and they all share the same auth layer:

  • REST API — the whole product surface, callable with a bearer token from any language.
  • API keys — the simplest credential: a cak_… token bound to one person in one church.
  • OAuth 2.0 & Connected Apps — per-church consent for multi-tenant apps; issues the same JWT a user gets.
  • Webhooks — signed, durably-delivered outbound events.
  • MCP server — an AI-facing wrapper over the REST API at /mcp.
  • Content providers — the inbound path for external media libraries into FreePlay and the B1 apps.

Everything except content providers is served by a single monolithic API (the Api repository) whose modules mount under stable base paths — /membership, /giving, /attendance, /content, /messaging, /doing, /reporting, and /mcp.

How It Fits Together

   ┌─────────────────────┐                          ┌───────────────────────────────────────┐
│ Third-party app │ Bearer cak_… / JWT │ B1 API (Api) │
│ · server / SaaS │ ───────────────────────▶ │ ┌─────────────────────────────────┐ │
│ · Zapier / Make │ │ │ CustomAuthProvider.getUser() │ │
│ · Google Sheets │ │ │ cak_ key ─┐ │ │
│ · CLI / scripts │ │ │ OAuth JWT ┴▶ Principal │ │
│ · AI client (MCP) │ ─── POST /mcp ──────────▶ │ │ scopes filter → permissions[] │ │
└─────────────────────┘ │ └────────────────┬────────────────┘ │
▲ │ ▼ │
│ │ API modules: /membership /giving │
│ signed JSON POST │ /attendance /content /messaging … │
│ (person / donation / group / …) │ │ │
└──────────── webhooks ◀─────────────────┼─ shared/webhooks/WebhookDispatcher │
(durable, HMAC-SHA256 signed) └───────────────────────────────────────┘

External content sources (Planning Center, Dropbox, Life.Church, CBN, …)
│ OAuth PKCE / device flow / none ── B1 is the OAuth *client* here ──▶

Packages/content-providers ──▶ FreePlay / B1 apps (inbound content path)

Three arrows tell the whole story: a third party calls in with a bearer token (API key or OAuth JWT, including via /mcp); the API calls back out through signed webhooks; and content providers are the one inbound-content path where B1 itself is the OAuth client pulling media from an external source.

The Shared Auth Model

Every credential — a user's login JWT, an OAuth access token, or an API key — resolves to the same Principal and is checked the same way. There is no separate "integration auth" path; a scoped credential is simply indistinguishable from a lower-privileged user.

JWT structure

B1 access tokens are HS256 JWTs minted in Api/src/modules/membership/auth/AuthenticatedUser.ts. The claim set:

ClaimMeaning
id, email, firstName, lastNameThe person behind the token
churchIdThe single church this token acts within — the anchor for all data scoping
personIdThe person record inside that church
permissionsFlat array of RBAC perm-strings ([apiName_]contentType_contentId_action)
groupIds, leaderGroupIdsGroup membership / leadership, for group-scoped checks
membershipStatusGuest vs. member, for self-service gating

An OAuth access token is byte-for-byte the same shape as a login JWT — the only difference is that its permissions array was filtered through the granted scopes before signing (getCombinedApiJwt(...)).

Per-church scoping

churchId is a token claim, not a request parameter, so a credential can never reach across churches. Every repository query filters on the caller's churchId; an API key or OAuth token is bound to exactly one church at mint time.

Role-based permissions at the boundary

Controllers gate actions with au.checkAccess(contentType, action) against the token's permissions array. Scopes are a filter, never a grant (Api/src/shared/auth/Scopes.ts): the SCOPE_CATALOG maps each scope (e.g. people:read, donations:write) to the RBAC pairs it permits, and filterPermissionsByScopes() intersects that with the person's current permissions on every resolve. Consequences:

  • Revoking a permission in B1Admin cuts the credential's access on the next request — tokens never drift from the role.
  • A scope can only ever remove permissions, so a scoped credential can never elevate to server / domain administration (those permissions are deliberately unmapped to any scope).
  • API keys carry a cak_ prefix; CustomAuthProvider.getUser() branches on it, hashes the secret, and re-resolves the owning person's live RBAC on each call.

See API Keys → Scopes for the full catalog.

Surface Reference

REST API

The complete product surface. Any authenticated endpoint accepts either a JWT or a cak_… API key in the Authorization: Bearer header — there is no separate key-only or OAuth-only route table. Modules and their base paths live under Api/src/modules/*.

API keys

A cak_<prefix>.<secret> personal access token, created in B1Admin → Settings → Developer → API Keys. Only a SHA-256 hash is stored; the raw key is shown once. Managed at /membership/apiKeys (Api/src/modules/membership/controllers/ApiKeyController.ts). Best for a single church's own scripts and for connectors like Zapier, Make, and Google Sheets. → API Keys

OAuth 2.0 & Connected Apps

For multi-tenant apps that need each church to consent. Implemented in Api/src/modules/membership/controllers/OAuthController.ts under /membership/oauth. The server supports three grants:

  • Authorization CodePOST /oauth/authorize (authenticated) returns a short-lived code; POST /oauth/token with grant_type=authorization_code exchanges it for an access JWT (≈ 7 days) plus a refresh token (≈ 90 days).
  • Device Code (RFC 8628) — POST /oauth/device/authorize issues a user_code; the user approves it in B1Admin (/oauth/device/approve); the device polls /oauth/token with the device-code grant. For TVs, kiosks, and CLIs with no browser.
  • Refresh Tokengrant_type=refresh_token mints a new access token; public (secret-less) clients may omit the secret.

A Connected App is the church-admin-facing view of a granted token, listed and revocable at /membership/oauth/connections. The controller also hosts an OAuth relay-session bridge (/oauth/relay/*) that lets a browserless device complete a sign-in against an external provider. → Connected Apps & OAuth

Webhooks

The only outbound surface. A church subscribes a public HTTPS endpoint to events; when a matching change occurs, WebhookDispatcher.emit(churchId, event, payload) records a delivery and a background worker POSTs a signed JSON envelope with retry/backoff and redelivery. Engine at Api/src/shared/webhooks/, per-church CRUD under /membership/webhooks (WebhookController.ts). A connectorType field reshapes the body for Slack / Discord. → Webhooks

MCP server

An AI-facing wrapper at /mcp (Api/src/modules/mcp/). Three generic tools — list_endpoints, describe_endpoint, api_call — expose the whole REST surface dynamically to any MCP client. Auth is the same bearer token as everything else, and api_call re-enters the Express stack in-process so every permission and church-scoping rule still applies. → MCP Server

Content providers

The inbound-content path, in the separate package Packages/content-providers (@churchapps/content-providers) rather than the API. Each provider implements the IProvider interface (src/interfaces.ts) — browse, getPlaylist, getInstructions, plus auth hooks — and self-registers into a Map registry (src/providers/registry.ts). Here B1 is the OAuth client: a provider declares an AuthType of none, oauth_pkce, device_flow, or form_login, and the shared helpers (OAuthHelper, DeviceFlowHelper, TokenHelper, ApiHelper) run the client-side PKCE / device flow against the external source. Eleven providers ship today — including Planning Center, Dropbox, Life.Church, CBN, BibleProject, Jesus Film, Lessons.church, and B1.church — feeding FreePlay and the B1 apps. → FreePlay Content Provider

Summary

SurfaceAuth mechanismDirectionWhere implementedReference
REST APIBearer JWT or cak_… keyInboundApi/src/modules/*API Keys
API keysSHA-256-hashed cak_ tokenCredentialApi/.../membership/controllers/ApiKeyController.tsAPI Keys
OAuth 2.0 / Connected AppsAuth code · device · refresh → JWTInboundApi/.../membership/controllers/OAuthController.tsConnected Apps
WebhooksPer-hook secret, HMAC-SHA256 signatureOutboundApi/src/shared/webhooks/ + WebhookController.tsWebhooks
MCP serverBearer JWT or cak_… keyInbound (AI)Api/src/modules/mcp/MCP Server
Content providersPer-provider: none / OAuth PKCE / device / formInbound contentPackages/content-providers/Content Provider

Prebuilt Connectors

Rather than everyone building from scratch, ChurchApps ships connectors on top of the surfaces above:

  • Slack & Discord — a webhook connectorType reshapes the standard envelope into a chat message; configured entirely in B1Admin, no third-party account.
  • Zapier and Make — trigger on webhook events and act via the REST API; they register their own webhook when a Zap/scenario turns on (needs a key with settings:write).
  • Google Sheets — an API-key-authenticated add-on that exports People / Donations / Groups / Attendance on demand.
  • Claude and ChatGPT — MCP clients pointed at /mcp.

For your own code, @churchapps/integration-sdk (Packages/integration-sdk) wraps all of it: a typed REST client, an OAuth client (auth-code / refresh / device flow), and an HMAC webhook verifier with Express middleware.