Technical reference
The stack, every API route, every environment variable, and how to run your own.
The stack#
- Framework
- Next.js 14 (App Router), React 18, TypeScript.
- Styling
- Tailwind plus extensive hand-written CSS; four Google fonts — Instrument Serif (display), Inter (body), Kalam (the Board’s handwriting), STIX Two Text (equations).
- Auth
- Clerk — middleware on every page and API route except static assets and the Studio.
- Storage
- Supabase Postgres, reached only from the server with the service-role key. The browser never talks to the database.
- Billing
- Stripe subscriptions, pinned API version, webhook-driven.
- Math
- KaTeX, rendered with
trust: falseand bounded size/expansion so hostile TeX cannot break layout or inject markup. - Journal
- Sanity (the blog and its Studio at
/studio). - Evals
- An A/B conversational eval replays 22 multi-turn scenarios (plus a depth-comparison case) against Core 3.1 with its controller on vs. off; deterministic checks always run, and a model judge joins when the eval is given an API key (
npm run eval:core3).
Model engines#
Each Socria model runs on its own underlying OpenAI engine, every one overridable per deployment, and the two premium models auto-retry on a fallback engine if the configured id is rejected — a mis-set model id never takes the product down:
| Surface | Default engine | Override | Fallback |
|---|---|---|---|
| Core 2 | gpt-4o-mini | OPENAI_MODEL | — |
| Core 3.1 | gpt-5.6-luna | OPENAI_MODEL_CORE_3 | gpt-4o |
| Logos | gpt-5.6-sol | OPENAI_MODEL_LOGOS (map: OPENAI_MODEL_LOGOS_MAP) | gpt-4o |
| Background passes | gpt-4o-mini | OPENAI_MEMORY_MODEL, OPENAI_INSIGHT_MODEL, OPENAI_SYNTHESIS_MODEL, OPENAI_JOURNEY_MODEL, OPENAI_STATE_MODEL | — |
API reference#
Auth comes in three flavors: account (Clerk session required), account/key (a session or a valid access key), and open. All model-calling routes are rate limited.
| Route | Auth | Does |
|---|---|---|
POST /api/chat | account/key¹ | Streams a Core reply; Core 3.1 adds the per-turn controller and semantic state pass |
GET·PUT·POST /api/conversations | account | List, upsert, and bulk-migrate conversations (chat and Logos both) |
DELETE /api/conversations/:id | account | Delete one conversation, scoped to its owner |
GET·PUT /api/profile | account | Imported AI-history profile + the Thinking Journey; partial updates only |
POST /api/extract-memory | account/key | Updates thread memory after each Core 3.1 exchange |
POST /api/generate-insight | account/key | One insight card, when earned |
POST /api/generate-synthesis | account/key | A structured synthesis, paced by depth |
POST /api/update-understanding | account/key | Refreshes the cross-conversation journey; guarded against wipes |
POST /api/logos/chat | account/key | Streams the Logos reply (optionally focused on one node) |
POST /api/logos/map | account/key | Re-extracts the Thinking Map; clamps depth and caps free maps server-side |
POST /api/logos/explore | account/key | The four node moves; free Research past the first returns 402 |
POST /api/logos/draft | account/key | The five Draft Space actions on a selected passage |
POST /api/logos/read | account/key | Reads an attached image once into text |
GET /api/logos/sources · POST …/search · POST …/fetch | account/key | Lists context sources; searches one; fetches one chosen item |
GET·DELETE /api/logos/connections, GET /api/logos/connect/:provider(/callback) | account/key³ | Per-user OAuth connections (dormant unless enabled) |
GET /api/logos/plan | open | The server’s answer on your plan; anonymous resolves to free |
POST /api/logos/redeem | open² | Redeems an access code; signed-in redemptions follow the account |
POST /api/stripe/checkout · portal | account | Start a subscription; open the billing portal |
POST /api/stripe/webhook | signature | The only writer of Stripe lifecycle state; verifies before parsing |
¹ Core 2 is open to anonymous users; Core 3.1 requires the account or key. ² Rate limited; typed codes are also checked on every account/key route, so this is the throttled front door rather than the only door — and the codes are soft gates by design, not secrets. ³ Reading connection status allows the key; connecting and disconnecting require the account.
Rate limits#
Fixed-window limits keyed by user id (or IP when anonymous), backed by Upstash Redis when configured and per-instance memory otherwise. Two buckets: chat (the expensive models) at 20/min · 400/day signed in and 8/min · 80/day anonymous; aux (background passes, node moves, billing surfaces) at 40/min · 1,200/day signed in and 20/min · 300/day anonymous. Over budget returns 429 with a Retry-After.
Database#
Four tables, defined idempotently in supabase/schema.sql:
conversations- Chat and Logos sessions in one table, told apart by
kind; messages, memory, the map, the draft and grounded contexts. On a database that predates the newer columns, those fields ride inside the memory JSON until the operator migrates — nothing is dropped. user_profiles- The imported AI-history profile and the Thinking Journey.
logos_connections- OAuth token bundles, AES-256-GCM encrypted with a key derived from
CONNECTION_SECRET— never plaintext. socria_subscriptions- The local projection of Stripe — lifecycle state written by the webhook, plus the pre-checkout customer stub and complimentary rows from redeemed codes.
Environment variables#
| Variable | Needed for |
|---|---|
OPENAI_API_KEY | Everything — the one hard requirement |
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY | Sign-in and cross-device sync |
SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY | Cloud-stored conversations |
NEXT_PUBLIC_SITE_URL | Canonical origin — metadata, OAuth redirects, Stripe return URLs |
STRIPE_SECRET_KEY, STRIPE_PRICE_SOCRIA_ONE, STRIPE_WEBHOOK_SECRET | Socria One billing (without them, access codes still work) |
SOCRIA_ONE_USER_IDS | Comma-separated Clerk ids comped onto One |
SERPER_API_KEY or TAVILY_API_KEY | Web research; with neither, Logos shows zero sources rather than inventing any |
NEXT_PUBLIC_SANITY_PROJECT_ID (+ dataset, api version) | The journal |
UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN | Cross-instance rate limits (in-memory fallback otherwise) |
CONNECTION_SECRET | Encrypting OAuth tokens (16+ chars); nothing stores without it |
SOCRIA_CONNECTORS + NEXT_PUBLIC_SOCRIA_CONNECTORS | Waking the dormant connectors (both must be on) |
GOOGLE_OAUTH_CLIENT_ID/SECRET, NOTION_OAUTH_CLIENT_ID/SECRET | The per-user OAuth apps, if connectors are on |
OPENAI_MODEL* family | Per-surface engine overrides (table above) |
RATE_LIMIT_DISABLED, SOCRIA_DISABLE_STATE | Kill switches: limiting off (effective in any environment — never set it in production) and Core 3.1’s state pass off |
Running your own#
- Set
OPENAI_API_KEY— the app runs with just this, local-only. - Add Clerk keys for accounts, and Supabase keys plus one run of
supabase/schema.sql(idempotent — safe to re-run) for cloud sync. - Add a search provider key if you want Research to cite anything.
- For billing: create the $15/month price in Stripe, point a webhook at
/api/stripe/webhooksubscribed tocheckout.session.completedandcustomer.subscription.created / updated / deleted, and set the three Stripe variables.
Everything degrades deliberately: no Supabase means local-only sessions, no Stripe means codes-only membership, no search key means sourceless research, no Sanity means no journal. Missing configuration turns features off; it does not break the ones that remain.