Brain API
Brain uses three classes of contracts:
- REST/HTTP — Clerk-authenticated endpoints for fennec/brisket and
@ApiKeyRoute()endpoints for service-to-service. - Outbound gRPC — Brain calls Round for model-serving work; it does not currently expose an inbound gRPC server.
- BullMQ queues — async generation, character refresh, and shop-VI import workflows.
Code paths referenced are absolute under apps/brain/.
Auth model summary
Section titled “Auth model summary”Both ClerkAuthGuard and ApiKeyAuthGuard are registered globally as APP_GUARD
(apps/brain/src/app.module.ts). Per-route metadata flips behavior:
| Decorator | Source | Effect |
|---|---|---|
@Public() |
common/decorators/public-route.decorator.ts |
Skips both guards. |
@ApiKeyRoute() |
common/decorators/api-key-route.decorator.ts |
Requires Authorization: Bearer <key> matching AUTHORIZED_KEYS. |
@Roles(...) |
common/decorators/roles.decorator.ts |
Adds RolesGuard requirement on top of Clerk; default required roles are ROOT, ADMIN. |
Cross-link: Auth Model, Auth Boundaries.
REST endpoints
Section titled “REST endpoints”Routes are grouped by controller. Auth column: Clerk = end-user JWT/OAuth via ClerkAuthGuard; API Key = AUTHORIZED_KEYS. Roles, where present, are enforced by RolesGuard.
Application settings — application-settings
Section titled “Application settings — application-settings”Source: apps/brain/src/modules/application/applicationSettings/controllers/application-settings.http.controller.ts
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /application-settings |
Clerk | List settings keys. |
| PUT | /application-settings/:id |
Clerk + Roles | Body must include data; Zod-validated, throws BadRequestException on shape mismatch. |
Storage — storage
Section titled “Storage — storage”Source: apps/brain/src/modules/application/storage/controllers/storage.http.controller.ts
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /storage/signed-url |
Clerk | S3/R2 GET URL. |
| GET | /storage/signed-urls |
Clerk | Batch GET URLs. |
| GET | /storage/public-url-template |
Clerk | Returns BRAIN_PUBLIC_BUCKET_URL template. |
API surface (service-to-service) — api/*
Section titled “API surface (service-to-service) — api/*”Sources: apps/brain/src/modules/domain/api/controllers/*.http.controller.ts
| Method | Path | Auth | Notes |
|---|---|---|---|
| POST | /api/onboarding/kitsune/image-score |
API Key | Score onboarding image. |
| GET | /api/onboarding/test-imgd/:key |
API Key | Internal diagnostic. |
| POST | /api/onboarding/test-cropping |
API Key | Internal diagnostic. |
| POST | /api/onboarding/reference-nudify |
API Key | Nudify 3 SFW reference images; returns nudified paths. See Reference Nudify. |
| POST | /api/webhook/rp/training |
API Key | Deprecated — throws NotImplementedException. |
| POST | /api/webhook/vi/created |
API Key | VI Generator batch-created webhook; calls ShopVIImportService.handleWebhookBatch. Returns {status: "accepted"}. |
| POST | /api/character/kitsune |
API Key | Provision Kitsune character. |
| GET | /api/character/vi/catalogue |
API Key | VI catalogue list. |
| GET/POST/PATCH/DELETE | /api/character, /api/character/:id |
API Key | Character CRUD used by sirloin. |
| GET/POST/PATCH/DELETE | /api/media, /api/media/:id |
API Key | Media CRUD used by sirloin (media.http.controller.ts). |
Character (admin) — character/*, body-type, dataset, character-control-group, dataset-scoring
Section titled “Character (admin) — character/*, body-type, dataset, character-control-group, dataset-scoring”Sources: apps/brain/src/modules/domain/character/controllers/*.http.controller.ts. All Clerk-authenticated, role-gated by default to ROOT, ADMIN via global RolesGuard.
Body Parts Library — body-parts-library
Section titled “Body Parts Library — body-parts-library”Source: apps/brain/src/modules/domain/character/controllers/body-parts-library.http.controller.ts. Clerk-authenticated, role-gated.
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /body-parts-library |
Clerk + Roles | List items, optional body_part filter. |
| POST | /body-parts-library |
Clerk + Roles | Upload reference image with body_part + attributes (multipart). Rejects values outside the taxonomy with 400. |
| DELETE | /body-parts-library/:id |
Clerk + Roles | Remove item and its stored image. |
Stores reference body-part images tagged with a body-part slot and filter
attributes. Slots, filters and their allowed values come from the
body-parts-library-taxonomy application setting, and the upload rejects any
slot, filter key or value the taxonomy does not declare. Read by the
character:pick_stock_body_reference workflow node and by the legacy nudify
endpoint, which do not share matching semantics. Managed via the Fennec Body
Parts Library page. See Body Parts Library
for the taxonomy, the wildcard matching rule and both consumers.
Reference Nudify
Section titled “Reference Nudify”Path: api/onboarding/reference-nudify
Optional character creation step that enables NSFW capabilities for users who choose not to upload real nude images. Instead of requiring actual NSFW photos, the system takes the user’s SFW onboarding images and generates synthetic NSFW reference variants using AI.
Accepts 3 SFW onboarding images (FACE_FRONTAL, FULL_BODY, FULL_BODY_ANY) and returns AI-generated nudified versions using WaveSpeed’s flux-2-klein-9b/edit-lora model with externally-hosted LoRA weights.
Request:
items[]– 3 objects withtype(KitsuneOnboardingImageType) andpath(R2 key)bust_size– one ofsmall,medium,big,hugegenitalia_style– one ofshaved,hairy
Behavior:
- Picks a random matching bust and genitalia reference image from the Body Parts Library.
- Reads LoRA configuration from
nudify-me-lora-inputapplication setting. - Resolves output size to maximize dimensions within 256–1536 px per side while preserving aspect ratio.
- Launches 3 concurrent WaveSpeed inference jobs with body-part-specific prompts.
- Polls for completion, downloads results, uploads to
characters/nudify-results/.
Response: Same 3 items with added nudified_path.
Source: apps/brain/src/modules/domain/character/services/nudify.service.ts, apps/brain/src/modules/domain/api/controllers/onboarding.http.controller.ts
Generation (admin) — generation/*, tags, presets, settings, examples, prompt-template, motion-type, input-image
Section titled “Generation (admin) — generation/*, tags, presets, settings, examples, prompt-template, motion-type, input-image”Sources: apps/brain/src/modules/domain/generation/controllers/*.http.controller.ts. Clerk-authenticated. The HTTP layer manages reference data; queue jobs do the heavy lifting.
Dashboard-specific generation endpoints back Fennec’s Generation Dashboard:
| Method | Path | Notes |
|---|---|---|
| GET | /generation/dashboard |
Cursor-paginated list of generations created in the rolling last 30 days. Rows are included even when adapter_params or logs are missing. Supports model, status, character, and media-type filters. |
| GET | /generation/dashboard/:id |
Single generation detail by genId; no recency cutoff, so older linked generations remain inspectable. Missing adapter params/logs return empty arrays. |
| GET | /generation/dashboard/models |
Model URL list used by the dashboard filter. |
The list cutoff is backed by idx_generation_dashboard_created_at_dbid on
Generation(created_at DESC, dbId DESC).
User — user/*
Section titled “User — user/*”Source: apps/brain/src/modules/domain/user/controllers/user.http.controller.ts. Resolves Brain user by Clerk ID; populates request.currentUser via RolesGuard.
Moderation — moderation/*
Section titled “Moderation — moderation/*”Source: apps/brain/src/modules/domain/moderation/controllers/moderation.http.controller.ts.
Spend — spend/*
Section titled “Spend — spend/*”Source: apps/brain/src/modules/domain/spend/controllers/spend.http.controller.ts.
Shop-VI — virtual-character-import/*
Section titled “Shop-VI — virtual-character-import/*”Source: apps/brain/src/modules/domain/shop-vi/controllers/virtual-character-import.http.controller.ts.
Bull Board UI — /queues
Section titled “Bull Board UI — /queues”Mounted by BullBoardModule.forRoot({ route: '/queues', adapter: ExpressAdapter }) in app.module.ts. Clerk-protected. Read-only inspector for all registered queues (bullBoardQueues.module.ts).
Standard error envelope
Section titled “Standard error envelope”All HTTP failures pass through HttpExceptionFilter
(apps/brain/src/common/filters/http-exception.filter.ts). Response shape:
{ "statusCode": 400, "timestamp": "2026-05-05T12:00:00.000Z", "path": "/api/character/abc", "message": "..."}5xx and unexpected errors are logged at error severity through Pino and exported to Axiom when log export is configured. See Brain Errors for code-by-code remediation.
Outbound gRPC
Section titled “Outbound gRPC”Brain does not currently start an inbound gRPC server; the former bootstrap block in main.ts is commented out. Generated Round client stubs live under apps/brain/src/generated/round/v1/round.ts, and apps/brain/src/modules/application/round/services/* calls Round over gRPC. Sirloin calls Brain over the generated HTTP/OpenAPI client.
BullMQ queues
Section titled “BullMQ queues”Queue names are defined in apps/brain/src/common/constants.ts:
export enum QUEUES { MEDIA_FLOWS = 'mediaflows', EXAMPLE_EXPLICITNESS = 'exampleexplicitness', SHOP_VI_IMPORT = 'shopviimport', KITSUNE_SFW_IMAGE_FROM_NSFW_GENERATION = 'kitsunesfwimagefromnsfwgeneration',}Connection is configured by apps/brain/src/config/queue.config.ts against REDIS_HOST / REDIS_PORT / REDIS_PASSWORD.
| Queue | Processor | Concurrency | Idempotency | Purpose |
|---|---|---|---|---|
mediaflows |
MediaFlowsProcessor |
500 | Job ID = generationRequest.mediaId (uuid); failures recorded via GenerationEventLogService.markError |
Drives the generation pipeline (image, image-seq, image-to-video, etc.). Catches ContentModerationException separately. |
exampleexplicitness |
ExampleExplicitnessProcessor |
1, batch 500 | Per tagId re-classification |
Re-classifies GenerationExample records when a tag’s explicitness changes. |
shopviimport |
ShopVIImportProcessor |
1 | Per webhook job_id from VI Generator |
Imports characters from a Shop-VI batch into the fennec schema. Triggered by POST /api/webhook/vi/created. |
kitsunesfwimagefromnsfwgeneration |
KitsuneSfwImageFromNsfwGenerationProcessor |
20 | Per characterId + nsfwSourceTypes |
Generates SFW reference images from NSFW sources for Kitsune characters. |
Default job options (apps/brain/src/config/queue.config.ts): completed jobs retained 24h or last 1,000; failed jobs retained 7 days or last 5,000.
flowchart LR sirloin -- HTTP --> brain brain -- enqueue --> mediaflows brain -- enqueue --> kitsune[kitsunesfwimagefromnsfwgeneration] brain -- enqueue --> shopvi[shopviimport] vigen[VI Generator] -- POST /api/webhook/vi/created --> brain brain -- gRPC --> round
Webhooks
Section titled “Webhooks”| Direction | Endpoint / Target | Source/Target | Auth | Notes |
|---|---|---|---|---|
| Inbound | POST /api/webhook/vi/created |
VI Generator (BRAIN_VI_GENERATOR_URL) |
API Key | See above. |
| Inbound | POST /api/webhook/rp/training |
RunPod (legacy) | API Key | Deprecated; returns 501. |
| Outbound | RunPod / FAL / Wavespeed / Kling / OpenRouter / Google Vertex | Provider URLs in env | Provider-specific | Driven from queue processors and adapter modules. |
Chargebee webhooks are not handled by brain — they terminate at sirloin (see Sirloin Billing).
Examples
Section titled “Examples”Service-to-service character lookup
Section titled “Service-to-service character lookup”curl -H "Authorization: Bearer $BRAIN_API_KEY" \ http://brain:3000/api/character/abc-123VI Generator webhook
Section titled “VI Generator webhook”curl -X POST -H "Authorization: Bearer $BRAIN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"job_id":"...","characters":[...]}' \ http://brain:3000/api/webhook/vi/created- TODO(@pawel): Confirm complete list of Clerk-authenticated
character/*andgeneration/*routes against current controller bodies (only headline routes documented here; controllers underapps/brain/src/modules/domain/character/controllers/andapps/brain/src/modules/domain/generation/controllers/). - Swagger UI is mounted at
/api-docswith the JSON document at/api-json(apps/brain/src/main.ts:70-74). - No GraphQL controllers or inbound gRPC microservice are registered; Brain exposes HTTP and acts as a gRPC client to Round (
apps/brain/src/main.ts:55-74).