Brisket API Surface
Brisket is the Next.js 16 user-facing frontend. Its public surface is small — most data flow happens through tRPC procedures over POST /api/trpc/[trpc] rather than bespoke REST endpoints. Upstream traffic terminates at sirloin (gRPC via ConnectRPC) plus a handful of third parties (Strapi, Intercom, Clerk).
Route Handlers
Source: apps/brisket/src/app/api/.
| Path | Method | Auth | Purpose | Source |
|---|---|---|---|---|
/api/health | GET | Public | Liveness probe — returns 200 { status: "ok" }. | app/api/health/route.ts |
/api/trpc/[trpc] | GET, POST | Clerk session via tRPC ctx | Single tRPC entrypoint. Uses @trpc/server/adapters/fetch fetchRequestHandler. | app/api/trpc/[trpc]/route.ts |
/api/webhook/clerk | POST | Svix HMAC (CLERK_WEBHOOK_SECRET) | Receives Clerk lifecycle webhooks (user.created, session.created) and emits PostHog events. | app/api/webhook/clerk/route.ts |
/api/health and /api/webhook/clerk are listed as public routes in apps/brisket/src/middleware.ts (isPublicRoute). All other routes — including the tRPC entrypoint — are gated by Clerk via auth.protect() in the same middleware.
Server Actions
Grep for "use server" returns no matches under apps/brisket/src (verified). All mutations go through tRPC. Use grep -rn '"use server"' apps/brisket/src to refresh; add server actions here if introduced.
tRPC Routers
Root composition: apps/brisket/src/server/api/root.ts. All procedures use protectedProcedure (Clerk-authenticated) unless explicitly marked otherwise. Inputs are validated with Zod; outputs from sirloin are returned as Connect/protobuf message shapes (pkg/sirloin/v5/*_pb). Error formatter (server/api/trpc.ts) attaches zodError to data for client-side field hints.
character — apps/brisket/src/server/api/routers/character.ts
Owns character CRUD, dataset uploads, and welcome-media triggers. Calls ctx.api (sirloin SirloinService).
| Procedure | Type | Input (Zod) | Output | Upstream |
|---|---|---|---|---|
getAllCharacters | query | – | Character[] | sirloin.listCharacters |
getCharacter | query | { character_id } | Character | null | sirloin.listCharacters (filtered) |
fetchCharacter / fetchCharacters | mutation | { character_id }? | Character[] | sirloin.listCharacters |
createCharacter | mutation | name, handles, Gender, CharacterModelType, pack id, is_nsfw | Character | sirloin.createCharacter |
getUploadRefImageUrl, getUploadImageUrls, getKitsuneUploadUrls | mutation | image meta | signed upload URLs | sirloin.getUpload* |
triggerRefImageUploaded | mutation | upload id | – | sirloin |
updateCharacter, deleteCharacter, updateCharacterReferenceDataset | mutation | character partials | Character | sirloin.updateCharacter / deleteCharacter |
verifyDatasetImage, removeDatasetImage | mutation | image id | – | sirloin |
generateKitsunePreviewImages, submitKitsuneCharacterForTraining | mutation | id | – | sirloin |
submitWelcomeMediaReview, triggerWelcomeMediaRegeneration, triggerWelcomeExDevOnly | mutation | – | – | sirloin |
scrapeCharacterInstagramHandle | mutation | handle | metadata | sirloin |
kyc — apps/brisket/src/server/api/routers/kyc.ts
Owns account-level KYC start/reset actions. Calls ctx.api (sirloin SirloinService).
| Procedure | Type | Input (Zod) | Output | Upstream |
|---|---|---|---|---|
startKYCVerification | mutation | { variant: "ri" | "vi" } | KYC payload | sirloin.startKYCVerification |
resetKYCVerification | mutation | – | empty response | sirloin.resetKYCVerification |
media — routers/media.ts
Image generation and library operations.
| Procedure | Type | Notes |
|---|---|---|
createImage, createMultipleImages | mutation | enqueue generation in sirloin |
getAllMedia | query | paginated library fetch |
toggleMediaFavoriteById, toggleMediaArchivedById | mutation | per-media flags |
fetchGlobalPending | query | pending generations across user |
reportMediaById | mutation | NSFW / abuse report |
markMediaDownload | mutation | analytics + watermark |
getLastMediaGenerationDate | mutation | rate-limit / banner data |
getFailedMediaByIds | mutation | retry/diagnostic |
subscription — routers/subscription.ts
Billing surface. Uses ctx.billingApi (sirloin BillingService) and validates Cloudflare Turnstile tokens server-side. See brisket-billing-flow.md for the end-to-end sequence.
Procedures: getCurrentUsage, claimOffer, setReferralCode, createPrimerCheckout, submitPaidInvoice, setTokenBillingAddress, createVaultingSession, listProducts, getCountryCode, updateNextBillingDate, getPrimerPaymentMethods, deletePrimerPaymentMethod, setPrimaryPrimerPaymentMethod, validateCouponCode, changePrimerSubscriptionBackToActive, getPrimerSubscriptions, getPrimerTransactions, getPrimerPaymentData, cancelPrimerSubscription, retryDunningPayment, cancelPrimerSubscriptionDowngrade.
profile — routers/profile.ts
getUserProfile (query), updateUserProfile (mutation, { nsfwEnabled? }). Both call ctx.api.getUserProfile / updateUserProfile on sirloin.
pack — routers/pack.ts
Starter packs and example browsing.
getFilteredExamples, getExampleById, getStarterPackList, getMediaTags — all call sirloin pack endpoints.
shop — routers/shop.ts
Virtual influencer shop.
listVIs, getVIdata, reserveCharacter, purchaseShopCharacterWithReservationToken — call sirloin shop endpoints.
intercom — routers/intercom.ts
createSupportConversation (mutation): looks up the contact in Intercom by primary email and posts a templated support message. Soft-fails (returns undefined) if INTERCOM_API_TOKEN is unset or contact missing.
faq — routers/faq.ts
getCheckoutFaqSection (query): fetches ${STRAPI_URL}/api/checkout-faq-section?populate=* with Authorization: Bearer ${STRAPI_TOKEN}. Cached via Next.js next: { revalidate: 3600 }.
Upstream Calls
flowchart LR Client[Browser] -->|POST /api/trpc/*| Brisket Brisket -->|gRPC ConnectRPC| Sirloin[(sirloin)] Brisket -->|HTTPS| Strapi[(chuck/Strapi)] Brisket -->|HTTPS| Intercom Clerk -->|svix webhook| Brisket- sirloin — primary upstream. Two clients constructed in
server/api/sirloin-api.ts:SirloinService(general — characters, media, profile, packs, shop) andBillingService(subscription router). Transport:createGrpcTransport({ baseUrl: env.SIRLOIN_URL, defaultTimeoutMs: 120_000, peerMaxConcurrentStreams: 500 }). Transport refreshed every 30 minutes viasetIntervalto mitigate stale H2 connections. Headers built byserver/api/grpc-headers.ts. - brain — no direct calls from brisket (verified — no
brainimports or HTTP/gRPC clients underapps/brisket/src). Brain is reached transitively via sirloin. - Strapi (chuck) — REST
GET /api/checkout-faq-sectionwith bearer token; ISR-cached for 1 hour. - Intercom — REST via
intercom-clientSDK (contacts.search,conversations.create). - Clerk — inbound webhook only; outbound auth is handled by
@clerk/nextjsmiddleware.
Anchor Docs
- Auth model: see
standards/auth-model. - Security model and Turnstile boundary:
standards/security-model. - Observability — tRPC tracing middleware emits OTel spans (
trpc.${type}.${path}) plusrecordTrpcRequestmetrics; seeoperations/observability.