Chuck API
Chuck API
Section titled “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
Section titled “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):
| Surface | Path prefix | Consumers |
|---|---|---|
| Content REST API | /api/... |
brisket (server-side) |
| Admin panel | /admin |
content 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
Section titled “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
Section titled “Collection types”Two collection types — both blog-shaped:
| API ID | Plural | Display |
|---|---|---|
blog |
blogs |
Academy (apps/chuck/meat/src/api/blog/content-types/blog/schema.json) |
blog-blog |
blog-blogs |
Blogs (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
Section titled “Single types”Single types are grouped by surface area in brisket. Naming convention is
<page>-<section> or <page>-shared-data.
| Group | Examples | Notes |
|---|---|---|
| Welcome page | welcome-hero-section, welcome-faq-section, welcome-shared-data, welcome-pricing-section, … |
Brisket public landing |
| AI homepage | ai-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 Creators | for-creators-hero-section, for-creators-pricing-section, for-creators-faq-section, for-creators-shared-data, … |
Creator funnel |
| Affiliates | affiliates-main-section, affiliates-faq-section, affiliates-become-affiliate-section, … |
|
| Showcase | showcase-hero-section, showcase-pricing-section, showcase-shared-data, … |
|
| Get Started | get-started-hero-section, get-started-videos-section, … |
Onboarding flow |
| Checkout / FAQ | checkout-faq-section, faq-section, cta-section, pricing-section, image-carousel-section |
Cross-page widgets |
| Policies | privacy-policy, refund-policy-page, terms-of-service |
Legal pages |
| Misc | featured-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
Section titled “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
Section titled “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:
| Method | Path | Notes |
|---|---|---|
GET |
/api/<plural-name> |
List (collection) / fetch (single) |
GET |
/api/<plural-name>/:id |
Fetch one (collection only) |
POST |
/api/<plural-name> |
Create — admin / authenticated |
PUT |
/api/<plural-name>/:id |
Update |
DELETE |
/api/<plural-name>/:id |
Delete |
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
Section titled “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
Section titled “Auth Model”Chuck has three independent token domains, all configured in
apps/chuck/meat/config/admin.ts:
| Token kind | Salt / secret env | Used by | Notes |
|---|---|---|---|
| Admin JWT | ADMIN_JWT_SECRET |
Admin panel session cookies | Issued on admin login |
| API tokens | API_TOKEN_SALT |
Service callers (brisket) | Created per-token in /admin → Settings → API Tokens. Sent as Authorization: Bearer <token>. |
| Transfer tokens | TRANSFER_TOKEN_SALT |
strapi transfer data export/import |
Used by ops, not runtime consumers |
| App keys | APP_KEYS (array) |
Koa session signing | Rotated 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
Section titled “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
Section titled “Plugins”From apps/chuck/meat/package.json and config/plugins.ts:
| Plugin | Version | Purpose |
|---|---|---|
@strapi/plugin-cloud |
5.5.1 | Strapi Cloud integration (idle if not used) |
@strapi/plugin-users-permissions |
5.5.1 | End-user auth + role permissions table |
@strapi/provider-email-nodemailer |
^5.9.0 | Email provider — wired in config/plugins.ts email block |
strapi-provider-cloudflare-r2 |
^0.3.0 | Upload provider — wired in config/plugins.ts upload block |
strapi-plugin-tablify |
^1.0.3 | Admin UI: table view in richtext editor |
Security Middleware
Section titled “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
Section titled “Anchor Docs”- Service overview: chuck and chuck-meat
- Env vars: chuck-env
- Errors: chuck-errors
- Runbook: chuck-runbook
- Local dev: chuck-local-dev
- Auth model: standards/auth-model
- Security model: standards/security-model