Skip to content

Brain API

Brain exposes three classes of contracts:

  1. REST/HTTP — Clerk-authenticated endpoints for fennec/brisket and @ApiKeyRoute() endpoints for service-to-service.
  2. gRPC — internal generation surface called by sirloin (generation.grpc.controller.ts).
  3. BullMQ queues — async generation, character refresh, and shop-VI import workflows.

Code paths referenced are absolute under apps/brain/.

Auth model summary

Both ClerkAuthGuard and ApiKeyAuthGuard are registered globally as APP_GUARD (apps/brain/src/app.module.ts). Per-route metadata flips behavior:

DecoratorSourceEffect
@Public()common/decorators/public-route.decorator.tsSkips both guards.
@ApiKeyRoute()common/decorators/api-key-route.decorator.tsRequires Authorization: Bearer <key> matching AUTHORIZED_KEYS.
@Roles(...)common/decorators/roles.decorator.tsAdds RolesGuard requirement on top of Clerk; default required roles are ROOT, ADMIN.

Cross-link: Auth Model, Auth Boundaries.

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

Source: apps/brain/src/modules/application/applicationSettings/controllers/application-settings.http.controller.ts

MethodPathAuthNotes
GET/application-settingsClerkList settings keys.
PUT/application-settings/:idClerk + RolesBody must include data; Zod-validated, throws BadRequestException on shape mismatch.

Storage — storage

Source: apps/brain/src/modules/application/storage/controllers/storage.http.controller.ts

MethodPathAuthNotes
GET/storage/signed-urlClerkS3/R2 GET URL.
GET/storage/signed-urlsClerkBatch GET URLs.
GET/storage/public-url-templateClerkReturns BRAIN_PUBLIC_BUCKET_URL template.

API surface (service-to-service) — api/*

Sources: apps/brain/src/modules/domain/api/controllers/*.http.controller.ts

MethodPathAuthNotes
POST/api/onboarding/kitsune/image-scoreAPI KeyScore onboarding image.
GET/api/onboarding/test-imgd/:keyAPI KeyInternal diagnostic.
POST/api/onboarding/test-croppingAPI KeyInternal diagnostic.
POST/api/onboarding/reference-nudifyAPI KeyNudify 3 SFW reference images; returns nudified paths. See Reference Nudify.
POST/api/webhook/rp/trainingAPI KeyDeprecated — throws NotImplementedException.
POST/api/webhook/vi/createdAPI KeyVI Generator batch-created webhook; calls ShopVIImportService.handleWebhookBatch. Returns {status: "accepted"}.
POST/api/character/kitsuneAPI KeyProvision Kitsune character.
GET/api/character/vi/catalogueAPI KeyVI catalogue list.
GET/POST/PATCH/DELETE/api/character, /api/character/:idAPI KeyCharacter CRUD used by sirloin.
GET/POST/PATCH/DELETE/api/media, /api/media/:idAPI KeyMedia CRUD used by sirloin (media.http.controller.ts).

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

Source: apps/brain/src/modules/domain/character/controllers/body-parts-library.http.controller.ts. Clerk-authenticated, role-gated.

MethodPathAuthNotes
GET/body-parts-libraryClerk + RolesList items, optional body_part filter.
POST/body-parts-libraryClerk + RolesUpload reference image with body_part + attributes (multipart).
DELETE/body-parts-library/:idClerk + RolesRemove item and its stored image.

Stores reference body-part images (bust, genitalia) with attribute metadata used by the nudify endpoint. Managed via the Fennec Body Parts Library page.

Reference Nudify — 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 with type (KitsuneOnboardingImageType) and path (R2 key)
  • bust_size — one of small, medium, big, huge
  • genitalia_style — one of shaved, hairy

Behavior:

  1. Picks a random matching bust and genitalia reference image from the Body Parts Library.
  2. Reads LoRA configuration from nudify-me-lora-input application setting.
  3. Resolves output size to maximize dimensions within 256—1536 px per side while preserving aspect ratio.
  4. Launches 3 concurrent WaveSpeed inference jobs with body-part-specific prompts.
  5. 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

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:

MethodPathNotes
GET/generation/dashboardCursor-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/:idSingle generation detail by genId; no recency cutoff, so older linked generations remain inspectable. Missing adapter params/logs return empty arrays.
GET/generation/dashboard/modelsModel 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/*

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/*

Source: apps/brain/src/modules/domain/moderation/controllers/moderation.http.controller.ts.

Spend — spend/*

Source: apps/brain/src/modules/domain/spend/controllers/spend.http.controller.ts.

Shop-VI — virtual-character-import/*

Source: apps/brain/src/modules/domain/shop-vi/controllers/virtual-character-import.http.controller.ts.

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

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 auto-reported to Sentry via @sentry/nestjs. See Brain Errors for code-by-code remediation.

gRPC

A single hybrid gRPC server is started in main.ts via app.startAllMicroservices(). Generated stubs live under apps/brain/src/generated/round/v1/round.ts (round client) and proto definitions are owned at the repo root in proto/.

Server-side controller:

FileService
apps/brain/src/modules/domain/generation/controllers/generation.grpc.controller.tsGeneration gRPC handlers consumed by sirloin.

Client (outbound) usage: apps/brain/src/modules/application/round/services/* calls round over gRPC. See round for the round-side contract.

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.

QueueProcessorConcurrencyIdempotencyPurpose
mediaflowsMediaFlowsProcessor500Job ID = generationRequest.mediaId (uuid); failures recorded via GenerationEventLogService.markErrorDrives the generation pipeline (image, image-seq, image-to-video, etc.). Catches ContentModerationException separately.
exampleexplicitnessExampleExplicitnessProcessor1, batch 500Per tagId re-classificationRe-classifies GenerationExample records when a tag’s explicitness changes.
shopviimportShopVIImportProcessor1Per webhook job_id from VI GeneratorImports characters from a Shop-VI batch into the fennec schema. Triggered by POST /api/webhook/vi/created.
kitsunesfwimagefromnsfwgenerationKitsuneSfwImageFromNsfwGenerationProcessor20Per characterId + nsfwSourceTypesGenerates 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 -- gRPC --> 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

DirectionEndpoint / TargetSource/TargetAuthNotes
InboundPOST /api/webhook/vi/createdVI Generator (BRAIN_VI_GENERATOR_URL)API KeySee above.
InboundPOST /api/webhook/rp/trainingRunPod (legacy)API KeyDeprecated; returns 501.
OutboundRunPod / FAL / Wavespeed / Kling / OpenRouter / Google VertexProvider URLs in envProvider-specificDriven from queue processors and adapter modules.

Chargebee webhooks are not handled by brain — they terminate at sirloin (see Sirloin Billing).

Examples

Service-to-service character lookup

Terminal window
curl -H "Authorization: Bearer $BRAIN_API_KEY" \
http://brain:3000/api/character/abc-123

VI Generator webhook

Terminal window
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

  • TODO(@pawel): Confirm complete list of Clerk-authenticated character/* and generation/* routes against current controller bodies (only headline routes documented here; controllers under apps/brain/src/modules/domain/character/controllers/ and apps/brain/src/modules/domain/generation/controllers/).
  • Swagger UI is mounted at /api-docs with the JSON document at /api-json (apps/brain/src/main.ts:70-74).
  • No GraphQL controllers are registered; brain exposes REST (HTTP) and a gRPC microservice (apps/brain/src/main.ts:55-66).