Skip to content

Chuck API

Chuck API

Chuck is the Strapi 5 headless CMS that serves marketing and policy content to brisket. The runtime lives in apps/chuck/meat/ (see chuck-meat for the service-local README pointer).

This page documents the HTTP surface, content model, and auth model. For environment variables see chuck-env.

Surface Map

Chuck exposes three HTTP surfaces, all served by the Strapi process bound to HOST:PORT (config/server.ts, defaults 0.0.0.0:1337, .env.example sets PORT=1333 for local dev):

SurfacePath prefixConsumers
Content REST API/api/...brisket (server-side)
Admin panel/admincontent editors
Admin REST API/admin/api, /users-permissions/*admin panel only

GraphQL is not enabled — the @strapi/plugin-graphql package is not listed in apps/chuck/meat/package.json (verified by grep graphql apps/chuck/meat/package.json apps/chuck/meat/config/plugins.ts, no hits). TODO(@marty): decide whether GraphQL should be enabled; current consumers (brisket) use REST.

Content Types

The content tree lives under apps/chuck/meat/src/api/<api-id>/content-types/<api-id>/schema.json. At time of writing: 94 content types (92 single-types, 2 collection-types) plus 21 component categories (~36 components total under apps/chuck/meat/src/components/).

Collection types

Two collection types — both blog-shaped:

API IDPluralDisplay
blogblogsAcademy (apps/chuck/meat/src/api/blog/content-types/blog/schema.json)
blog-blogblog-blogsBlogs (apps/chuck/meat/src/api/blog-blog/content-types/blog-blog/schema.json)

Both share fields: image (media), title, shortDescription, content (richtext), author (component author.author), slug (regex ^[a-z0-9]+(?:-[a-z0-9]+)*$).

TODO(@marty): decide which of blog / blog-blog is canonical — the duplicate suggests one is legacy and should be retired. Both directories exist (apps/chuck/meat/src/api/blog/, apps/chuck/meat/src/api/blog-blog/) with identical default routers (createCoreRouter); brisket call sites do not currently reference either by name (no STRAPI_URL consumer hits /api/blogs or /api/blog-blogs in apps/brisket/src).

Single types

Single types are grouped by surface area in brisket. Naming convention is <page>-<section> or <page>-shared-data.

GroupExamplesNotes
Welcome pagewelcome-hero-section, welcome-faq-section, welcome-shared-data, welcome-pricing-section, …Brisket public landing
AI homepageai-homepage-hero-section, ai-homepage-faq-section, ai-homepage-shared-data, …Variant landing
Homepage (legacy)homepage-hero-section, homepage-faq-section, homepage-shared-data, …TODO(@marty): confirm retirement — both homepage-* and ai-homepage-* directories coexist under apps/chuck/meat/src/api/, with no brisket reference to either in apps/brisket/src (only checkout-faq-section is currently wired).
For Creatorsfor-creators-hero-section, for-creators-pricing-section, for-creators-faq-section, for-creators-shared-data, …Creator funnel
Affiliatesaffiliates-main-section, affiliates-faq-section, affiliates-become-affiliate-section, …
Showcaseshowcase-hero-section, showcase-pricing-section, showcase-shared-data, …
Get Startedget-started-hero-section, get-started-videos-section, …Onboarding flow
Checkout / FAQcheckout-faq-section, faq-section, cta-section, pricing-section, image-carousel-sectionCross-page widgets
Policiesprivacy-policy, refund-policy-page, terms-of-serviceLegal pages
Miscfeatured-in, testimonial, testimonial-section, blog-section, academy-section

Per-page shared-data singles (welcome-shared-data, ai-homepage-shared-data, etc.) hold cross-section settings — site name, logos, OG tags, default CTA copy. Example fields in apps/chuck/meat/src/api/welcome-shared-data/content-types/welcome-shared-data/schema.json: websiteName, mobileLogo, desktopLogo, copyrightTextAndSlogan, ctaButtonText, ogTitle, ogDescription, keywords, …

Components

Components live under apps/chuck/meat/src/components/<category>/<name>.json and are re-used across content types. Categories:

affiliates, author, core-benefits, creator (7 components), faq, gallery-item, key-point (2), perk, preview-image, pricing (2), problem, problem-solution, quote, reach-us, review, rich-content (4), showcase (3), socials, switch, testimonial (2), video.

REST Endpoints

Strapi auto-generates a CRUD route set per content type via factories.createCoreRouter (see apps/chuck/meat/src/api/blog-section/routes/blog-section.ts). For each single-type and collection-type with the default router:

MethodPathNotes
GET/api/<plural-name>List (collection) / fetch (single)
GET/api/<plural-name>/:idFetch one (collection only)
POST/api/<plural-name>Create — admin / authenticated
PUT/api/<plural-name>/:idUpdate
DELETE/api/<plural-name>/:idDelete

Strapi 5 query params: ?populate=*, ?populate[field]=..., ?filters[field][$eq]=..., ?fields=..., ?locale=..., ?status=draft|published. All content types declare "options": { "draftAndPublish": true }.

Concrete consumer evidence

brisket fetches the checkout FAQ section over REST (apps/brisket/src/server/api/routers/faq.ts:22):

const url = `${env.STRAPI_URL}/api/checkout-faq-section?populate=*`;
const res = await fetch(url, {
headers: { Authorization: `Bearer ${env.STRAPI_TOKEN}` },
});

STRAPI_URL and STRAPI_TOKEN are required env in apps/brisket/src/env.js:31-32. Any other brisket → chuck call sites should follow the same pattern. At time of writing, the only brisket fetcher of STRAPI_URL is apps/brisket/src/server/api/routers/faq.ts (verified via grep -rn 'STRAPI_URL' apps/brisket/src).

Auth Model

Chuck has three independent token domains, all configured in apps/chuck/meat/config/admin.ts:

Token kindSalt / secret envUsed byNotes
Admin JWTADMIN_JWT_SECRETAdmin panel session cookiesIssued on admin login
API tokensAPI_TOKEN_SALTService callers (brisket)Created per-token in /admin → Settings → API Tokens. Sent as Authorization: Bearer <token>.
Transfer tokensTRANSFER_TOKEN_SALTstrapi transfer data export/importUsed by ops, not runtime consumers
App keysAPP_KEYS (array)Koa session signingRotated by adding new keys to the front of the array

The users-permissions plugin (@strapi/plugin-users-permissions@5.5.1, apps/chuck/meat/package.json) is installed but not currently used as a consumer auth path — brisket uses an API token. End-user accounts on chuck would also need JWT_SECRET (see .env.example); TODO(@marty): confirm whether any product flow needs end-user auth on chuck — no brisket call site references the chuck users-permissions endpoints today.

Anchor: the multi-service auth picture lives in standards/auth-model; the trust-boundary picture lives in standards/security-model. Chuck → brisket is a static-bearer-token boundary.

Permissions

The “Public” role permissions (which find / findOne are anonymous on each content type) are stored in the database (Strapi users-permissions), not in code. There is no policy file checked into apps/chuck/meat/src/api/*/routes/ — every router file uses factories.createCoreRouter(...) with no policies override. brisket’s only known fetch path (apps/brisket/src/server/api/routers/faq.ts) sends Authorization: Bearer ${env.STRAPI_TOKEN}, so the API-token path is exercised; TODO(@marty): confirm whether any anonymous (Public-role) read paths exist in production and document the role-bootstrap step in chuck-runbook if so.

Plugins

From apps/chuck/meat/package.json and config/plugins.ts:

PluginVersionPurpose
@strapi/plugin-cloud5.5.1Strapi Cloud integration (idle if not used)
@strapi/plugin-users-permissions5.5.1End-user auth + role permissions table
@strapi/provider-email-nodemailer^5.9.0Email provider — wired in config/plugins.ts email block
strapi-provider-cloudflare-r2^0.3.0Upload provider — wired in config/plugins.ts upload block
strapi-plugin-tablify^1.0.3Admin UI: table view in richtext editor

Security Middleware

apps/chuck/meat/config/middlewares.ts adds connect-src, img-src, media-src allowlists pointing at market-assets.strapi.io and the R2 public URL (CF_PUBLIC_ACCESS_URL). strapi::cors is enabled with its default config (no overrides in apps/chuck/meat/config/middlewares.ts, which means the Strapi default origin: '*' applies) — TODO(@marty): decide whether the origin allowlist needs tightening for production.

Anchor Docs