Skip to main content
Docs
Open console

MemoryNode API usage

Canonical HTTP reference for the memorynode-api Cloudflare Worker. Source of truth for behavior is code in apps/api/src/. The regenerated docs/external/openapi.yaml documents the public product surface (run pnpm openapi:gen) — internal, operator, and advanced routes may still exist in the Worker but are omitted from that contract.

  • Production base: https://api.memorynode.ai
  • Hosted MCP base: https://mcp.memorynode.ai (same Worker)
  • Staging base: https://api-staging.memorynode.ai
  • Local dev: http://127.0.0.1:8787

Product focus: make users feel remembered for Indian SaaS founders shipping AI chat or copilots inside their product (PRODUCT.md).

Advanced surfaces (connectors read settings, audit exports, pruning metrics, admin routes, etc.) may exist on the Worker for operators; they are omitted from the published OpenAPI contract — see section 10.

OpenAPI is a subset: openapi.yaml covers Tier A memory/search/context/usage only. Dashboard session routes, billing, and hosted MCP are documented in this file (§7) — not in OpenAPI. Do not generate a client from OpenAPI alone for console setup or PayU checkout.

1. Authentication

Dispatched in apps/api/src/auth.ts and apps/api/src/lifecycle/handleRequestImpl.ts.

ModeTriggerValidationUses
API key (K)Authorization: Bearer <key> or x-api-key: <key>SHA-256 of key + API_KEY_SALT then authenticate_api_key(p_key_hash) RPCAll /v1/* tenant routes
Dashboard session (S)Cookie: mn_dash_session=<opaque> + x-csrf-tokenRow in dashboard_sessions + CSRF double-submit/v1/* tenant routes from browser
Admin (A)x-admin-tokenEquality against MASTER_ADMIN_TOKEN or HMAC-SHA256 signed form; optional IP allowlist (ADMIN_ALLOWED_IPS); ADMIN_BREAK_GLASS/admin/*, /v1/admin/*
PayU webhook (H)form bodyReverse SHA-512 over PayU response fields using PAYU_MERCHANT_SALT (see payuReconcile.ts)POST /v1/billing/webhook only
Internal MCP (I)x-internal-mcp: 1 + x-internal-secret: <MCP_INTERNAL_SECRET>Constant-time compareInternal hosted-MCP → REST subrequests
Public (P)/healthz, /ready, /v1/health

Customers: create and revoke keys only via the console dashboard routes (POST /v1/dashboard/api-keys, GET /v1/dashboard/api-keys, POST /v1/dashboard/api-keys/revoke) while signed in — see §6.7. Do not use /v1/api-keys in app code or quickstarts.

Operators: POST /v1/api-keys, GET /v1/api-keys, and POST /v1/api-keys/revoke require x-admin-token (MASTER_ADMIN_TOKEN). They are internal/operator-only and are not in the generated OpenAPI contract.

All API keys are rate-limited at 15 RPM for the first 48 h after api_keys.created_at; after that the default is 60 RPM per key. See packages/shared/src/plans.ts.

Webhook forwarding uses internal headers after signature verification, but that internal token path is route-bound to POST /v1/memories and is not accepted as a general-purpose auth bypass on other routes.

1.1 Auth quick reference (which client uses what)

You are building…UseDo not use
Production SaaS backend (Node, Python, etc.)API key (K) on POST /v1/memories and POST /v1/searchDashboard session cookie from the browser
MemoryNode console (Playground, setup)Dashboard session (S)POST /v1/dashboard/init then cookie + CSRF on mutating callsPasting a live API key into Playground
Creating keys for your appDashboard sessionPOST /v1/dashboard/api-keysPOST /v1/api-keys (operator x-admin-token only)
Cursor / Claude Desktop / agent IDEAPI key (K) via REST/SDK first; optional MCP (MCP_SERVER.md)MCP as the only integration path for your product backend
PayU billing callbackPayU webhook (H) on POST /v1/billing/webhookAPI key or session
On-call / cron / hygieneAdmin (A) on /admin/*Customer-facing docs or quickstarts

Integrator rule: ship with REST + @memorynodeai/sdk (transport: "rest" default). Use the same owner_id as your app's logged-in user id.

Contributor map (monorepo layers, not customer copy): docs/internal/ARCHITECTURE.md.

2. API surface tiers

Use this map when writing docs, SDK examples, and console copy. Tier A is what new developers should see first. Tier B is for advanced integrations. Tier C is operator-only — never put Tier C routes in customer quickstarts.

Tier A — Customer activation (OpenAPI + quickstart)

RoutePurpose
POST /v1/memoriesSave memory
POST /v1/searchRecall memory
POST /v1/contextAssembled context for prompts
GET /v1/usage/todayPlan caps vs usage
POST /v1/dashboard/initConsole one-shot setup (session cookie + workspace)
GET /v1/dashboard/workspacesList workspaces for signed-in user
POST /v1/dashboard/api-keysCreate API key (plaintext returned once)
GET /v1/dashboard/api-keysList masked keys
POST /v1/dashboard/api-keys/revokeRevoke a key
GET /v1/billing/statusEntitlement / plan status
POST /v1/billing/checkoutPayU checkout form
GET /v1/health, GET /healthzHealth
GET /v1/memories, GET/DELETE /v1/memories/:idList / read / delete
Hosted MCP (/v1/mcp)Secondary — 7 tools; see MCP_SERVER.md (not in OpenAPI v1)

Tier B — Power-user (Worker + this doc; not quickstart-first)

RoutePurpose
POST /v1/ingestAdvanced ingest dispatch
POST /v1/memories/conversationTranscript ingest

Tier C — Internal / operator only (not in public OpenAPI)

Route familyAuthNotes
/admin/*, /v1/admin/*x-admin-tokenWebhook reprocess, usage reconcile, session cleanup, memory hygiene/retention, billing health
POST /v1/workspacesAdmin API key or operator toolingLegacy workspace create
GET/POST /v1/api-keys, POST /v1/api-keys/revokex-admin-tokenOperator key management — not for customers
POST /v1/billing/webhookPayU signatureServer-to-server only
GET /v1/dashboard/overview-statsDashboard sessionConsole-only metrics
3. Middleware order

From handleRequestImpl in apps/api/src/lifecycle/handleRequestImpl.ts (health short-circuit in healthRoutes.ts):

  1. Request-id resolve, CORS + security headers ensemble.
  2. Short-circuit health endpoints (/healthz, /ready, /v1/health).
  3. CORS deny if Origin is not in ALLOWED_ORIGINS (except hosted MCP paths).
  4. enforceRuntimeConfigGuards and ensureRateLimitDo.
  5. OPTIONS short-circuit.
  6. assertBodySize (bounded by MAX_BODY_BYTES / MAX_IMPORT_BYTES).
  7. KNOWN_PATH_ALLOWED_METHODS 405 gate with Allow: header.
  8. Production dashboard ALLOWED_ORIGINS gate.
  9. createSupabaseClient(env) + db_access_path_selected log.
  10. Hosted MCP path → IP rate limit → handleHostedMcpRequest.
  11. Dashboard session POST/logout inline.
  12. Enforce route-level auth and handler dispatch via route().
  13. Build handlerDeps, instantiate factories, call route().
  14. 404 if route() returns null.
  15. Catch: ApiError-shaped response with error_code; else 500 INTERNAL.
  16. Finally: emitAuditLog and request_completed structured log.
4. Error envelope

Errors emitted by the outer catch in handleRequestImpl (apps/api/src/lifecycle/handleRequestImpl.ts) use:

{ "error": { "code": "STRING_CODE", "message": "human readable" } }

Typical codes: BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, PAYLOAD_TOO_LARGE, RATE_LIMITED, CAP_EXCEEDED, TRIAL_EXPIRED, COST_BUDGET_EXCEEDED, INTERNAL. Correlate responses with the x-request-id header.

5. Rate limits, concurrency, quotas

All defined in apps/api/src/auth.ts, apps/api/src/usage/quotaReservation.ts, and packages/shared/src/plans.ts.

ControlDefaultSource
Per-key RPM60 (15 for new keys, 48 h)RATE_LIMIT_MAX, RATE_LIMIT_RPM_NEW_KEY
Per-workspace RPM120 (300 for scale)WORKSPACE_RPM_DEFAULT, WORKSPACE_RPM_SCALE
Per-workspace in-flight8WORKSPACE_CONCURRENCY_MAX, TTL 30000 ms
Cost/minute burst15 INRWORKSPACE_COST_PER_MINUTE_CAP_INR
Daily and period capsatomic via Postgresreserve_usage_if_within_cap / commit_usage_reservation
Global AI cost budgetfail-closed (prod)AI_COST_BUDGET_INR, 60 s cache

When entitlement verification is unavailable, quota-consuming routes move into temporary read-only degradation (ENTITLEMENT_DEGRADED, HTTP 503) until billing checks recover.

Trial gate (quota-consuming routes)

After auth, handlers resolve entitlement and today's usage, then run the trial gate before cap reservation:

StepModule
Quota resolveapps/api/src/usage/quotaResolution.ts
Usage lookupapps/api/src/usage/dailyUsage.ts
Trial gateapps/api/src/trialWrites.ts + packages/shared/src/trialAccess.ts
Cap reserveapps/api/src/usage/quotaReservation.ts

Trial workspaces: blocked when 14 days pass or trial usage limits are hit → HTTP 402 TRIAL_EXPIRED (includes upgrade_required). Paid plans at cap → daily_cap_exceeded / CAP_EXCEEDED instead.

Hosted MCP write tools use the same trial rule before mutating; see MCP_SERVER.md.

Chat completions provider (Worker)

Optional multi-vendor chat routing (default OpenAI): CHAT_PROVIDER = openai | anthropic | gemini in apps/api/src/env.ts. Keys: ANTHROPIC_API_KEY, GEMINI_API_KEY (Google AI Studio); OPENAI_API_KEY still powers embeddings when EMBEDDINGS_MODE=openai and powers chat when CHAT_PROVIDER=openai. Optional CHAT_MODEL overrides the default cheap model for the active chat provider. Implementation: apps/api/src/llm/chatComplete.ts.

Embeddings are not swapped when changing CHAT_PROVIDER. The memory_chunks table stores embedding_provider, embedding_dimension, and embedding_version per chunk; vector search only compares rows matching the query embedding dimension (default 1536 for legacy OpenAI/stub). No bulk re-embed is required for existing data.

Memory lifecycle intelligence (automatic)

MemoryNode applies deterministic lifecycle rules on ingest, search, and nightly hygiene — no extra routes beyond normal writes and search.

SignalBehavior
DedupeCanonical hash, normalized near-text, or embedding similarity can return an existing memory_id with deduped: true and optional dedupe_kind (near, semantic)
Confidence / volatilityPersisted on the memory row; used in Worker ranking (not in raw SQL recall scores)
Ephemeral TTLHigh-volatility text may get expires_at; expired rows move to archived via the hygiene job
Supersessionreplaces_memory_id, conflict resolution, evolution merges, and admin hygiene set lifecycle_state: replaced
Retrieval learningSearch/context bump implicit learning signals on the Worker (no public POST /v1/feedback in v1). Log x-request-id for support.

Implementation: apps/api/src/memories/memoryPolicyEngine.ts, apps/api/src/pipelines/search/ranking.ts, migrations 076078.

What shows up in HTTP responses today:

SurfaceFields
POST /v1/memories (dedupe hit){ memory_id, stored: true, deduped: true, dedupe_kind? }
POST /v1/memories (new row)memory_id, stored, chunks, extraction, optional intelligence.conflict_state, optional trace.memory_evolution_*, optional superseded_memory_id
POST /v1/searchresults, pagination — no retrieval_trace in the JSON body
POST /v1/contextcontext_text, citations, pagination — same search pipeline, assembled for prompts
Retrieval architecture: Postgres RPCs (match_chunks_vector, match_chunks_text) perform recall only (similarity / ts_rank + filters). Confidence, lifecycle, type, learning, and freshness ranking run in the Worker after RRF fusion (078_search_recall_only_rpcs.sql).

Debugging without custom tooling:

  • Log x-request-id on every call (support correlation).
  • Search body field explain: true adds ranking_reason string arrays on each hit (and optional ranking_reasons on the response). Intended for development and support — see DEBUGGING.md. Slight extra work on the Worker; omit in hot paths if you do not need reasons.
  • Chunk rows store embedding_provider, embedding_dimension, embedding_version (default 1536-dim OpenAI/stub behavior).
6. Plans

From packages/shared/src/plans.ts. PlanId = "trial" | "build" | "deploy" | "scale".

Self-serve checkout currently offers Build only; Deploy and Scale quota rows are reference material for written-agreement tiers.

PlanINRPeriod (d)WritesReadsEmbed tokGen tokStorage GBRetention (d)Workspace RPM
trial01410050050 00000.13060
build1 499301 0002 500500 000150 000190120
deploy4 999305 00015 0003 000 0002 500 0005180120
scale14 9993020 00050 00010 000 0008 000 00020365300

Overage rates per 1 k / per 1 M tok / per GB-mo are hard-coded per plan in plans.ts:87-203.

7. Routes (tenant-facing)

Dispatch is in apps/api/src/router.ts. K = API key. S = dashboard session.

7.1 Memories

MethodPathAuthPurpose
POST/v1/memoriesK/SCreate a memory; embed, chunk; extract defaults to false — set extract: true to opt in to LLM child-memory extraction when plan allows
POST/v1/memories/conversationK/SCreate from transcript or messages (transforms → /v1/memories)
GET/v1/memoriesK/SPaginated list, filters: namespace, user_id, owner_id, owner_type, memory_type, start_time, end_time, metadata
GET/v1/memories/:idK/SSingle memory
DELETE/v1/memories/:idK/SCascade delete with chunks
POST/v1/ingestK/SAdvanced ingest dispatch → memory / conversation / document-as-text

7.2 Search and context

MethodPathAuthPurpose
POST/v1/searchK/SEmbed query, pgvector search, optional rerank; header x-save-history: true inserts search_query_history
POST/v1/contextK/SSearch + context assembly with citations

Search request fields (`POST /v1/search`, `POST /v1/context`)

Validated in apps/api/src/contracts/search.ts, normalized in apps/api/src/search/normalizeRequest.ts, executed in apps/api/src/pipelines/search/executeSmartMemorySearch.ts.

FieldValuesWhat it does
search_modehybrid (default), vector, keywordRecall strategy: hybrid runs vector + keyword SQL recall with RRF fusion; vector is embedding match only; keyword is full-text match only (no query embed).
retrieval_profilebalanced (default), recall, precisionScore floor preset for min_score (not a separate API mode). recall defaults to a lower floor (0.08, capped at 0.2); precision defaults higher (0.32, floored at 0.25); balanced uses your min_score or no extra default.
min_score01 optionalDrop hits below this ranking-derived score after fusion.
explaintrue / omitWhen true, each result may include ranking_reason (string codes). retrieval_trace is not returned on the public search body.
top_k, page, page_size, filtersPagination and metadata/time/type filters (see schema).

Default smart search (recommended): omit search_mode (uses hybrid) and omit retrieval_profile (uses balanced). The Worker runs the memory planner, SQL recall, fusion, optional rerank (plan-gated), and Worker-side ranking in executeSmartMemorySearch.ts.

Not a request field: recall_only — Postgres RPCs perform recall-only scoring; final ranking always runs in the Worker (ranking_layer: "worker" in internal diagnostics only).

Explain / debug: set explain: true on the search body. For routing debug in non-production, optional request header x-mn-debug-routing: 1 adds x-mn-resolved-container-tag and related headers (see §9).

Owner identity (`owner_id` / `user_id`)

Memories and search are scoped per end-user within your workspace:

  • Pass owner_id (or user_id, same value) = your SaaS app's logged-in user id for every write and read in production.
  • Use owner_type (user | team | app) when you scope memories to teams or app-level actors; default is user.
  • If user_id and owner_id are both omitted, the API uses the canonical demo owner default-user (packages/shared/src/ownerDefaults.ts) — fine for Playground and quickstarts only.
  • Multi-tenant products must not mix real users under default-user; session 1 and session 2 will not see each other's memories unless they share that id.
  • In dev and staging only, when the default owner was applied because you omitted owner fields, responses may include x-mn-default-owner-used: true. Production does not emit this header.

7.3 Usage

MethodPathAuthPurpose
GET/v1/usage/todayK/SCaps vs consumed reads/writes/tokens; includes entitlement_active and entitlement_source (billing)

7.5 Workspaces and API keys

Customers (Tier A): use dashboard session routes in §7.7 (below):

  • GET/POST /v1/dashboard/workspaces
  • GET/POST /v1/dashboard/api-keys, POST /v1/dashboard/api-keys/revoke

The console App connection page (/app-connection) is the supported UI for creating keys. Plaintext keys are returned once on create; list responses are masked (key_prefix, key_last4 only).

Operators (Tier C): POST /v1/workspaces, GET/POST /v1/api-keys, and POST /v1/api-keys/revoke require x-admin-token. They use the same hashing and masked storage shape as the dashboard create_api_key RPC but are not customer-facing and must not appear in quickstart snippets or public OpenAPI.

7.6 Billing

Active path: PayU checkout in INR (POST /v1/billing/checkoutPOST /v1/billing/webhook). Customer plan codes and period caps come from packages/shared/src/plans.ts via the entitlements row (resolveQuotaForWorkspace in apps/api/src/usage/quotaResolution.ts). Legacy Stripe and Launch plan names are historical only — not offered in checkout.

Internal USD/INR cost guards (AI_COST_BUDGET_INR, WORKSPACE_COST_PER_MINUTE_CAP_INR) are operator spend ceilings, not customer checkout currency.

PayU checkout is the supported billing path (POST /v1/billing/checkout).

MethodPathAuthNotes
GET/v1/billing/statusK/Sselect entitlements
POST/v1/billing/checkoutK/SBody: {plan, firstname?, email?, phone?}. Inserts payu_transactions, computes SHA-512 request hash, returns {url, method:"POST", fields} for the PayU form.
POST/v1/billing/webhookHPayU callback. Verifies reverse SHA-512 (or HMAC-SHA256 fallback), calls PayU verify API, upserts entitlements.

7.7 Dashboard

Browser console routes use the dashboard session cookie (S) and request-scoped Supabase execution. Mutating POST routes also require a valid x-csrf-token (double-submit with the session bootstrap). JSON bodies use { ok: true, data: … } on success or { ok: false, error: { code, message, details? } } on failure unless noted.

MethodPathAuthNotes
POST/v1/dashboard/initSupabase JWT in body{ access_token, workspace_name? }. Resolves or creates workspace, sets dashboard session cookie, returns { workspace_id, name, created, workspaces, csrf_token }. Preferred console setup path.
POST/v1/dashboard/sessionSupabase JWT in body{access_token, workspace_id}; verifies via SUPABASE_JWT_SECRET, inserts dashboard_sessions, sets HttpOnly cookie, returns csrf_token. Used for workspace switch / session rebind after init (returning users).
POST/v1/dashboard/bootstrapRetired (removed). Use /v1/dashboard/init instead.
POST/v1/dashboard/logoutSDeletes session, clears cookie.
GET/v1/dashboard/overview-statsSdashboard_console_overview_stats RPC.
GET/v1/dashboard/workspacesSLists workspaces the signed-in user belongs to (id, name, role).
POST/v1/dashboard/workspacesS + CSRF{ name }; create_workspace RPC.
GET/v1/dashboard/api-keysSQuery workspace_id? (defaults to active session workspace; must match session). list_api_keys RPC.
POST/v1/dashboard/api-keysS + CSRF{ workspace_id, name }; create_api_key RPC (returns plaintext key once).
POST/v1/dashboard/api-keys/revokeS + CSRF{ api_key_id }; revoke_api_key RPC.

Not shipped in v1: team invites and member management (/v1/dashboard/invites, /v1/dashboard/members, …) are not routed on the public Worker yet. The console does not expose team UI. Do not call these paths until documented in a future release.

7.8 Health

MethodPathAuthNotes
GET/healthzPValidates critical env, returns version + embedding_model.
GET/readyPCircuit-breaker-wrapped get_api_key_salt RPC.
GET/v1/healthPSame payload as /healthz.

7.9 Admin

All auth with x-admin-token.

MethodPathNotes
POST/admin/webhooks/reprocessRerun reconcilePayUWebhook for deferred events
POST/admin/usage/reconcileprocess_usage_reservation_refunds() RPC
POST/admin/sessions/cleanupDelete expired dashboard_sessions
POST/admin/memory-hygienefind_near_duplicate_memories(...); query: dry_run, limit, workspace_id
POST/admin/memory-retentionArchive per retention; query: limit
GET/v1/admin/billing/healthBilling health view

7.10 MCP (secondary)

REST + SDK remain the primary integration path for SaaS backends. Hosted MCP is for builder tools (Cursor, Claude Desktop).

  • POST /v1/mcp, POST /mcp → Streamable HTTP JSON-RPC (handleHostedMcpRequest in apps/api/src/handlers/mcp/hosted.ts).
  • GET /v1/mcp, GET /mcp → browser landing or SSE.
  • DELETE /v1/mcp, DELETE /mcp → close session.
  • Session header mcp-session-id after initialize. 409 when the session registry points to another isolate — clients must re-initialize and retry (see MCP_SERVER.md).
  • MCP_ADVANCED_TOOLS_ENABLED defaults off in production; P1 memory/search tools do not require it.

See docs/MCP_SERVER.md for tools, policy, and production vars.

8. Canonical flows

8.1 `POST /v1/memories`

auth → rate limit → concurrency lease → quota reserve → embed (EMBEDDINGS_MODE, versioned chunk metadata) → dedupe / conflict / evolution passes → insert memories + memory_chunks → optional LLM extraction via CHAT_PROVIDER (up to 10 child memories) → commit reservation → audit.

resolveQuotaForWorkspace uses the billing entitlement row source (entitlements).

Entitlement source changes are audited in workspace_entitlement_audit and write-protected after create.

8.2 `POST /v1/search`

auth → rate limit → read reservation → optional query embed → SQL recall (vector and/or keyword RPCs) → Worker RRF fusion + ranking (+ optional rerank) → implicit retrieval-learning signals → JSON { results, page, page_size, total, has_more } (no retrieval_trace in body). Header x-save-history: true stores history without trace snapshot.

8.3 `POST /v1/billing/checkout`

Insert payu_transactions row → build SHA-512 request hash (buildPayURequestHashInput) → return {url, method:"POST", fields}. The dashboard auto-submits the form.

8.4 `POST /v1/billing/webhook`

Verify reverse SHA-512 (or HMAC-SHA256 fallback) → verifyPayUTransactionViaApi with retry and timeout → upsertWorkspaceEntitlementFromTransaction200. Idempotent via payu_webhook_events.

Operator verification (no new tooling):

  1. Staging drill: pnpm payu:smoke, pnpm payu:webhook-test (see DEPLOYMENT.md).
  2. Checkout: POST /v1/billing/checkout returns PayU form fields; dashboard submits to PayU; return URL hits console billing page.
  3. Deferred webhooks: POST /admin/webhooks/reprocess with x-admin-token reruns reconcilePayUWebhook (see runbook §33).
  4. Correlation: every response includes x-request-id; search logs for billing_webhook_*, webhook_failed, or entitlement errors on that id.
  5. Health: GET /v1/admin/billing/health (admin token).
9. Client headers you may see
HeaderMeaning
x-request-idCorrelation id on every response
x-mn-default-owner-usedtrue when owner_id / user_id was omitted and default-user was applied (dev/staging only)
x-mn-resolved-container-tagResolved tenant container (non-prod debug, or with x-mn-debug-routing: 1)
x-mn-routing-modeIsolation routing mode (non-prod debug)
Retry-AfterSeconds (429 responses)
10. Operator and internal routes (not in OpenAPI)

These routes may exist on the Worker for operators and internal tooling. They are not part of the public product surface and are omitted from OpenAPI.

MethodPathAuthNotes
GET/v1/connectors/settingsK/SOperator-only connector settings — not customer-facing
11. Changes and drift

docs/external/openapi.yaml is generated from code by apps/api/scripts/generate_openapi.mjs. CI enforces OpenAPI drift with pnpm openapi:check. The generator intentionally documents a reduced product surface (core memory, search, context, usage, billing checkout/status, dashboard init/session/workspaces/api-keys, health, MCP). Operator routes (/admin/*, /v1/admin/*, PayU webhooks, connectors, audit, pruning, etc.) remain in the Worker and are described in this file where relevant, but are not duplicated as paths in OpenAPI.

Keep API truth docs aligned in the same PR (see .cursor/rules/documentation-governance.mdc).

Recent backend hardening keeps API behavior unchanged while making optional internals fail-safe in degraded or stubbed environments: learned-adjustment lookup, monthly LLM usage reads, retrieval-attribute loading, and async feedback persistence now no-op safely when dependent storage/query capabilities are unavailable. The dev/CI smoke path (pnpm smoke:ci, which boots wrangler dev with SUPABASE_MODE=stub) also auto-registers the in-memory Supabase stub on first request when stage is non-production; production stages reject SUPABASE_MODE=stub outright in apps/api/src/db/createSupabaseClient.ts so no external API behavior is affected.

Admin and billing webhook routes are served directly by the main Worker.

Type to search all pages. navigate · Enter open · Esc close