Socria Docs
Technical

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: false and 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:

SurfaceDefault engineOverrideFallback
Core 2gpt-4o-miniOPENAI_MODEL
Core 3.1gpt-5.6-lunaOPENAI_MODEL_CORE_3gpt-4o
Logosgpt-5.6-solOPENAI_MODEL_LOGOS (map: OPENAI_MODEL_LOGOS_MAP)gpt-4o
Background passesgpt-4o-miniOPENAI_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.

RouteAuthDoes
POST /api/chataccount/key¹Streams a Core reply; Core 3.1 adds the per-turn controller and semantic state pass
GET·PUT·POST /api/conversationsaccountList, upsert, and bulk-migrate conversations (chat and Logos both)
DELETE /api/conversations/:idaccountDelete one conversation, scoped to its owner
GET·PUT /api/profileaccountImported AI-history profile + the Thinking Journey; partial updates only
POST /api/extract-memoryaccount/keyUpdates thread memory after each Core 3.1 exchange
POST /api/generate-insightaccount/keyOne insight card, when earned
POST /api/generate-synthesisaccount/keyA structured synthesis, paced by depth
POST /api/update-understandingaccount/keyRefreshes the cross-conversation journey; guarded against wipes
POST /api/logos/chataccount/keyStreams the Logos reply (optionally focused on one node)
POST /api/logos/mapaccount/keyRe-extracts the Thinking Map; clamps depth and caps free maps server-side
POST /api/logos/exploreaccount/keyThe four node moves; free Research past the first returns 402
POST /api/logos/draftaccount/keyThe five Draft Space actions on a selected passage
POST /api/logos/readaccount/keyReads an attached image once into text
GET /api/logos/sources · POST …/search · POST …/fetchaccount/keyLists 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/planopenThe server’s answer on your plan; anonymous resolves to free
POST /api/logos/redeemopen²Redeems an access code; signed-in redemptions follow the account
POST /api/stripe/checkout · portalaccountStart a subscription; open the billing portal
POST /api/stripe/webhooksignatureThe 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#

VariableNeeded for
OPENAI_API_KEYEverything — the one hard requirement
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEYSign-in and cross-device sync
SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEYCloud-stored conversations
NEXT_PUBLIC_SITE_URLCanonical origin — metadata, OAuth redirects, Stripe return URLs
STRIPE_SECRET_KEY, STRIPE_PRICE_SOCRIA_ONE, STRIPE_WEBHOOK_SECRETSocria One billing (without them, access codes still work)
SOCRIA_ONE_USER_IDSComma-separated Clerk ids comped onto One
SERPER_API_KEY or TAVILY_API_KEYWeb 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_TOKENCross-instance rate limits (in-memory fallback otherwise)
CONNECTION_SECRETEncrypting OAuth tokens (16+ chars); nothing stores without it
SOCRIA_CONNECTORS + NEXT_PUBLIC_SOCRIA_CONNECTORSWaking the dormant connectors (both must be on)
GOOGLE_OAUTH_CLIENT_ID/SECRET, NOTION_OAUTH_CLIENT_ID/SECRETThe per-user OAuth apps, if connectors are on
OPENAI_MODEL* familyPer-surface engine overrides (table above)
RATE_LIMIT_DISABLED, SOCRIA_DISABLE_STATEKill switches: limiting off (effective in any environment — never set it in production) and Core 3.1’s state pass off

Running your own#

  1. Set OPENAI_API_KEY — the app runs with just this, local-only.
  2. Add Clerk keys for accounts, and Supabase keys plus one run of supabase/schema.sql (idempotent — safe to re-run) for cloud sync.
  3. Add a search provider key if you want Research to cite anything.
  4. For billing: create the $15/month price in Stripe, point a webhook at /api/stripe/webhook subscribed to checkout.session.completed and customer.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.