.github/9
ISSUE_TEMPLATE/3bug_report.md config.yml feature_request.md
workflows/3ci.yml clean-clone.yml release.yml
CODEOWNERS dependabot.yml pull_request_template.md
apps/630
api/250
src/243
common/30
__tests__/3authorize.test.ts bigint-serialization.interceptor.test.ts files-limits.test.ts
filters/1all-exceptions.filter.ts
guards/3auth.guard.ts internal-service.guard.ts rate-limit.guard.ts
interceptors/2actor-context.interceptor.ts bigint-serialization.interceptor.ts
middleware/2idempotency.test.ts idempotency.ts
pipes/1zod-validation.pipe.ts
actor-context.ts audit.service.ts cache.test.ts cache.ts cookies.ts core.module.ts deprecation.ts email.service.ts errors.ts event-bus.ts log-scrubber.test.ts log-scrubber.ts logger.ts permission-cache-bridge.ts prisma.service.ts redis.service.ts trace-metadata.ts types.ts
config/1env.ts
lib/11
__tests__/5crypto.test.ts filter-builder.test.ts jwt.test.ts pagination.test.ts positions.test.ts
authorize.ts crypto.ts filter-builder.ts jwt.ts pagination.ts positions.ts
modules/197
ai/28
__tests__/4ai-writing.test.ts database-ai.test.ts generate-executor.test.ts prompts.test.ts
generator/4executor.ts generate.controller.ts generate.dto.ts generate.service.ts
ai-chat.service.ts ai-rate-limit.ts ai-writing.controller.ts ai-writing.dto.ts ai-writing.service.ts ai.controller.ts ai.module.ts ai.service.ts budget-guard.service.ts database-ai.controller.ts database-ai.dto.ts database-ai.service.ts db-prompts.ts dto.ts gateway-provider.ts ledger.service.ts prompts.ts rag.controller.ts rag.dto.ts rag.service.ts
api-keys/3api-keys.controller.ts api-keys.module.ts api-keys.service.ts
audit/2audit-log.controller.ts audit.module.ts
auth/4auth.controller.ts auth.module.ts auth.service.ts dto.ts
automations/5automations.controller.ts automations.module.ts automations.service.ts dto.ts webhook-incoming.controller.ts
blocks/5
__tests__/1batch-version-cas.test.ts
block.service.ts blocks.controller.ts blocks.module.ts dto.ts
comments/4comments.controller.ts comments.module.ts comments.service.ts dto.ts
databases/9
__tests__/3normalize-select-options.test.ts option-id-canonicalization.test.ts record-open-query-shape.test.ts
database.service.ts databases.controller.ts databases.module.ts dto.ts property-cache-schema.ts query.service.ts
docs/2docs.controller.ts docs.module.ts
files/10
__tests__/1quota.test.ts
dto.ts files.controller.ts files.module.ts files.service.ts quota.ts record.ts s3.ts uploads.controller.ts uploads.service.ts
forms/18debug.controller.ts form.dto.ts forms.controller.ts forms.module.ts forms.service.ts markdown.test.ts markdown.ts optional-auth.guard.ts public-forms.controller.ts public-forms.service.ts question.dto.ts questions.service.ts rate-limit.ts recaptcha.ts slug.ts submissions.controller.ts submissions.service.ts submit-schema.ts
health/3health.controller.ts health.module.ts health.service.ts
integrations/3integrations.controller.ts integrations.module.ts integrations.service.ts
internal/12
__tests__/2flush-query-shape.test.ts internal.endpoints.test.ts
ai-test-seam.controller.ts ai-test-seam.dto.ts ai-test-seam.service.ts internal.controller.ts internal.dto.ts internal.module.ts internal.service.ts test-seam.controller.ts test-seam.dto.ts test-seam.service.ts
notifications/3notifications.controller.ts notifications.module.ts notifications.service.ts
oauth/6
__tests__/1oauth.scopes.test.ts
oauth.controller.ts oauth.module.ts oauth.service.ts scopes.ts token-keys.ts
org/15audit.service.ts embed-preview.controller.ts guards.ts members.service.ts org-admin.controller.ts org-policy.service.ts org.module.ts overview.service.ts policy-effective.controller.ts policy.schema.ts policy.test.ts retention.service.ts totp.test.ts totp.ts two-factor.service.ts
orgs/5dto.ts orgs.controller.ts orgs.module.ts orgs.service.ts util.ts
pages/4dto.ts page.service.ts pages.controller.ts pages.module.ts
scim/15
__tests__/3email-mapping.test.ts etag.test.ts filters.test.ts
etag.ts filters.ts groups.service.ts schemas.ts scim-admin.controller.ts scim-admin.service.ts scim-auth.guard.ts scim-auth.service.ts scim-errors.ts scim.controller.ts scim.module.ts users.service.ts
search/4
__tests__/1search.service.test.ts
search.controller.ts search.module.ts search.service.ts
sessions/3sessions.controller.ts sessions.module.ts sessions.service.ts
share/3share.controller.ts share.module.ts share.service.ts
sso/16
__tests__/4enforcement.test.ts oidc.verify.test.ts provisioning.test.ts saml.validate.test.ts
enforcement.ts oidc.controller.ts oidc.service.ts oidc.ts provisioning.service.ts saml.controller.ts saml.ts schemas.ts sso-admin.controller.ts sso-admin.service.ts sso-secrets.ts sso.module.ts
teams/4dto.ts teams.controller.ts teams.module.ts teams.service.ts
users/4dto.ts users.controller.ts users.module.ts users.service.ts
webhooks/3webhooks.controller.ts webhooks.module.ts webhooks.service.ts
workspaces/4dto.ts workspaces.controller.ts workspaces.module.ts workspaces.service.ts
types/1xmldom.d.ts
app.module.ts constants.ts main.ts
_probe_rawbody.cjs .env.example Dockerfile package.json README.md tsconfig.json vitest.config.ts
realtime/33
src/17auth.ts comments.ts config.ts event-source.ts http-store.ts index.ts metrics.ts notifications.ts page-updated-source.ts presence.ts prisma-store.ts protocol.ts rate-limit.ts room-key.ts rooms.ts server.ts update-log.ts
test/11auth.test.ts config.test.ts event-source.test.ts http-store.test.ts metrics-merge.test.ts page-updated-source.test.ts presence.test.ts protocol.test.ts rate-limit.test.ts rooms.test.ts task-0094-builders.test.ts
.env.example Dockerfile package.json README.md tsconfig.json
web/255
src/244
app/45
__tests__/1theme-tokens.test.ts
(app)/26
w/25
[workspace]/25
ask/1page.tsx
databases/1
[id]/1page.tsx
favorites/1page.tsx
files/1page.tsx
notifications/1page.tsx
page/4
[id]/4
[block]/1page.tsx
share/1page.tsx
layout.tsx page.tsx
search/1page.tsx
settings/10
appearance/1page.tsx
automations/3
[id]/1page.tsx
new/1page.tsx
page.tsx
billing/1page.tsx
developers/1page.tsx
members/1page.tsx
permissions/1page.tsx
teams/1page.tsx
page.tsx
shared/1page.tsx
templates/1page.tsx
trash/1page.tsx
layout.tsx page.tsx
layout.tsx
(auth)/4
login/1page.tsx
register/1page.tsx
reset-password/1page.tsx
layout.tsx
api/2
metrics/1route.ts
v1/1
health/1route.ts
f/1
[slug]/1page.tsx
health/1
ready/1route.ts
org/2
[orgId]/2
admin/2
integrations/1page.tsx
page.tsx
share/1
[id]/1page.tsx
error.tsx global-error.tsx globals.css layout.tsx not-found.tsx page.tsx providers.tsx
components/118
admin/10
org-admin/8ApiKeysTab.tsx AuditTab.tsx MembersTab.tsx OverviewTab.tsx PoliciesTab.tsx RetentionTab.tsx WebhooksTab.tsx WorkspacePicker.tsx
ScimCard.tsx SsoTabs.tsx
ai/6ai-panel.tsx ask-ai-submenu.tsx db-ai-dialog.tsx generate-page-dialog.tsx writing-actions-menu.tsx writing-diff.tsx
automations/5action-editor.tsx condition-editor.tsx lib.ts run-history.tsx trigger-editor.tsx
collaboration/6CursorLayer.tsx OfflineBanner.tsx PresenceBar.tsx README.md ReviewFailedDialog.tsx StatusChip.tsx
comments/3comment-panel.tsx comment-thread.tsx mention-input.tsx
database/27
__tests__/2cell-editor.test.tsx cell-renderer.test.tsx
AiExtractDialog.tsx AiFormulaDialog.tsx AiGenerateDialog.tsx board-view.tsx buttons-panel.tsx calendar-view.tsx cell-editor.tsx cell-renderer.tsx chart-view.tsx database-page-view.tsx database-toolbar.tsx database-view-renderer.tsx db-embedded.tsx filter-builder.tsx form-builder-view.tsx formula-editor.tsx gallery-view.tsx group-builder.tsx list-view.tsx property-editor.tsx record-header.tsx record-panel.tsx sort-builder.tsx table-view.tsx timeline-view.tsx
dialogs/7
__tests__/1move-to-dialog.test.tsx
create-dialog.tsx dialog.tsx file-permissions-dialog.tsx invite-dialog.tsx move-to-dialog.tsx share-dialog.tsx
editor/19
__tests__/3code-block-text.test.ts code-block.test.tsx expandable.test.tsx
block-card.tsx block-renderer.tsx block-save-queue.ts block-toolbar.tsx context-menu.tsx expandable.tsx history-popover.tsx inline-editor.tsx insert-dialog.tsx page-canvas.tsx page-spinner.tsx page-view.tsx selection-toolbar.tsx slash-menu.tsx template-dialog.tsx type-menu.tsx
files/4FileBlock.tsx FileBrowser.tsx FilePicker.tsx PreviewModal.tsx
form/4form-field.tsx form-renderer.tsx form-responses-view.tsx form-view.tsx
layout/8
__tests__/1page-tree-icon.test.tsx
command-palette.tsx factory-logo.tsx page-tree-node.tsx sidebar.tsx theme-provider.tsx topbar.tsx workspace-shell.tsx
mentions/3
__tests__/1mention-model.test.ts
mention-model.ts MentionPopup.tsx
search/3ask-ai-search.tsx results-list.tsx search-dialog.tsx
settings/10
api-keys/4ApiKeyCreateDialog.tsx ApiKeyRevealDialog.tsx ApiKeyTable.tsx ScopesBadge.tsx
webhooks/5CurlExamples.tsx DeliveryInspector.tsx EventExplorer.tsx OverviewTab.tsx WebhooksTab.tsx
settings-shell.tsx
ui/3confirm-dialog.tsx form.tsx toaster.tsx
hooks/21
__tests__/4realtime-frame-handler.test.ts use-blocks.test.ts use-database-options.test.ts use-workspace.test.tsx
realtime-frame-handler.ts use-ai-stream.ts use-auth.ts use-blocks.ts use-database.ts use-db-ai-stream.ts use-forms.ts use-members.ts use-mention-sources.ts use-notifications.ts use-offline-status.ts use-pages.ts use-presence-announcer.ts use-presence-toasts.ts use-presence.ts use-realtime.ts use-workspace.ts
lib/50
__tests__/21ai-database.test.ts ai-generate.test.ts ai-sse.test.ts ai-writing.test.ts api-keys.test.ts api-members.test.ts auth-refresh.test.ts auth-store.test.ts batch-queue-drop.test.ts block-content.test.ts clipboard-import.test.ts code-highlight.test.ts offline-status.test.ts presence-announcer.test.ts presence-color.test.ts presence-cursor.test.ts presence-emitter.test.ts realtime-url.test.ts search.test.ts utils.test.ts writing-diff.test.ts
files/7
__tests__/2picker.test.ts upload.test.ts
api.ts picker.ts upload-store.ts upload.ts use-file-uploads.ts
ai-database.ts ai.ts api-keys.ts api.ts auth-store.ts batch-queue.ts block-content.ts clipboard-import.ts code-highlight.ts constants.ts offline-status.ts org-admin.ts presence-announcer.ts presence-color.ts presence-cursor.ts presence-editor-views.ts presence-emitter.ts query-client.ts realtime.ts search.ts types.ts utils.ts
stores/8
__tests__/1block-patch-store.test.ts
block-patch-store.ts comment-pill-store.ts database-store.ts editor-store.ts offlineQueue.ts presence-store.ts ui-store.ts
instrumentation.ts middleware.ts
.env.example Dockerfile next-env.d.ts next.config.mjs package.json postcss.config.mjs README.md tailwind.config.ts tsconfig.json tsconfig.tsbuildinfo vitest.config.ts
worker/92
src/87
__tests__/13ai-embedding.test.ts ai-request.test.ts ai-usage-budget.test.ts audit-export.test.ts email-decision.test.ts encrypt.test.ts health-search-index-lag.test.ts mime.test.ts retention.test.ts s3-sign.test.ts search-index.test.ts smtp.test.ts webhook-sign.test.ts
health/1server.ts
lib/12
__tests__/1clamav.test.ts
clamav.ts encrypt.ts job-otel.ts mime.ts notify.ts prisma.ts redis-stream.ts redis.ts s3.ts sharp.ts smtp.ts
processors/56
automations/28
__tests__/3condition-evaluator.spec.ts executors.spec.ts run-pipeline.spec.ts
executors/17assign-user.ts call-integration.ts change-status.ts classify.ts create-notification.ts create-page.ts email-cap.ts helpers.ts integration-registry.ts permission.ts record-write.ts registry.ts run-ai-action.ts send-email.ts send-webhook.ts types.ts update-record.ts
condition-evaluator.ts dead-letter.ts log.ts matcher.ts pipeline.ts run-state.ts schedule.ts watchdog.ts
files/15
__tests__/4errors.test.ts scan-gate.test.ts status.test.ts temp.test.ts
steps/4finalize.ts metadata.ts scan.ts thumbnails.ts
cleanup.ts errors.ts generateThumbs.ts processor.ts status.ts temp.ts types.ts
search-index/4builders.ts grants.ts index.ts processor.ts
ai-embedding.ts ai.processor.ts audit-export.ts automations.processor.ts email-decision.processor.ts email.processor.ts maintenance.processor.ts retention.ts webhooks.processor.ts
config.ts events.ts index.ts logger.ts queues.ts
.env.example Dockerfile package.json README.md tsconfig.json
docs/73
api/17ai.md authentication.md automations.md blocks.md comments.md databases.md files.md forms.md openapi.yaml org-admin.md pages.md scim.md search.md sso.md versioning.md webhooks.md workspaces.md
architecture/10ai-architecture.md automation.md backend-architecture.md database-architecture.md frontend-architecture.md realtime-architecture.md search-architecture.md security-architecture.md storage.md system-architecture.md
database/2database-architecture.md redis-keys.md
development/8admin-guide.md coding-standards.md deployment.md development-guide.md files-pipeline.md perf-report.md performance.md testing.md
product/4functional-requirements.md non-functional-requirements.md product-requirements.md ux-requirements.md
requirements/1requirements-matrix.md
security/5audit-report.md files.md forms.md license-allowlist.md security-architecture.md
tasks/16phase-00-foundation.md phase-01-authentication.md phase-02-workspace.md phase-03-pages.md phase-04-editor.md phase-05-databases.md phase-06-relations.md phase-07-collaboration.md phase-08-search.md phase-09-files.md phase-10-forms.md phase-11-automation.md phase-12-ai.md phase-13-api.md phase-14-enterprise.md phase-15-production.md
ui/9ai.md automations.md design-system.md developers.md files.md forms.md org-admin.md scim.md sso.md
support-matrix.md
infrastructure/71
caddy/1Caddyfile
docker/6
init/101-extensions.sql
opensearch/1init-indices.json
.env.docker docker-compose.yml opensearch-init.sh README.md
kubernetes/40
observability/20
alertmanager/1alertmanager.yaml
grafana/8
dashboards/6ai.json api-overview.json infra.json realtime.json search.json workers-queues.json
provisioning/2
dashboards/1dashboards.yaml
datasources/1datasource.yaml
prometheus/3alerts.yaml recording-rules.yaml scrape.yaml
scripts/1sync-sources.mjs
alertmanager.yaml grafana.yaml kustomization.yaml namespace.yaml node-exporter.yaml otel-collector.yaml prometheus.yaml
postgres/7configmap.yaml cronjob-backup.yaml cronjob-opensearch-snapshot.yaml pgbouncer.yaml replica.yaml statefulset.yaml walg-iam-policy.yaml
api.yaml configmap.yaml ingress.yaml kustomization.yaml namespace.yaml networkpolicy.yaml pdb.yaml README.md realtime.yaml redis.yaml secret.example.yaml web.yaml worker.yaml
nginx/2nginx.conf README.md
observability/14
alertmanager/1alertmanager.yaml
grafana/8
dashboards/6ai.json api-overview.json infra.json realtime.json search.json workers-queues.json
provisioning/2
dashboards/1dashboards.yaml
datasources/1datasource.yaml
prometheus/3alerts.yaml recording-rules.yaml scrape.yaml
scripts/1dashboard-completeness.mjs
README.md
terraform/8compute.tf data.tf main.tf network.tf outputs.tf README.md security.tf variables.tf
packages/289
ai/24
src/20
providers/7anthropic.ts google.ts http.ts index.ts openai-compatible.ts rag.test.ts writing.test.ts
schemas/2database.ts generate.ts
adapters.test.ts config.test.ts config.ts gateway.test.ts gateway.ts index.ts tools.test.ts tools.ts types.ts usage.test.ts usage.ts
package.json README.md tsconfig.json vitest.config.ts
auth/11
src/7index.ts passwords.test.ts passwords.ts sessions.test.ts sessions.ts tokens.test.ts tokens.ts
package.json README.md tsconfig.json vitest.config.ts
collaboration/17
src/13awareness.ts codec.test.ts codec.ts index.ts lww.test.ts lww.ts mapping.ts offline.ts outbox.test.ts outbox.ts provider.test.ts provider.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
config/7
src/4eslint.base.js index.ts prettier.base.json tsconfig.base.json
package.json README.md tsconfig.json
database-client/5
src/2index.test.ts index.ts
package.json README.md tsconfig.json
database-engine/33
src/29
filter/2filter.test.ts index.ts
formula/8evaluator.ts formula.test.ts functions.ts index.ts lexer.ts parser.ts types.ts validate.ts
query/6group.ts index.ts paginate.ts pivot.ts query.test.ts sort.ts
relations/2index.ts relations.test.ts
rollups/2index.ts rollups.test.ts
values/5dates.ts index.ts registry.ts types.ts values.test.ts
views/3buttons.ts index.ts views.test.ts
index.ts
package.json README.md tsconfig.json vitest.config.ts
editor/25
src/21
marks/1highlight.ts
nodes/2equation.ts mention.ts
autosave.test.ts autosave.ts clipboard.test.ts clipboard.ts codec.test.ts codec.ts context-menu.test.ts context-menu.ts diff.test.ts diff.ts factory.ts history.test.ts history.ts index.ts positions.ts slash.test.ts slash.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
observability/20
src/16
__tests__/8db-redis-metrics.test.ts metrics.test.ts otel.test.ts pino-otel.test.ts prometheus.test.ts resource.test.ts scrubber.test.ts traceparent.test.ts
index.ts metrics.ts otel.ts pino-otel.ts prometheus.ts resource.ts scrubber.ts traceparent.ts
package.json README.md tsconfig.json vitest.config.ts
permissions/11
src/7index.ts levels.test.ts levels.ts resolve.test.ts resolve.ts rules.test.ts rules.ts
package.json README.md tsconfig.json vitest.config.ts
search/22
src/18chunking.test.ts chunking.ts client.test.ts client.ts embeddings.test.ts embeddings.ts hybrid.test.ts hybrid.ts index.ts indexer.test.ts indexer.ts mapping.test.ts mapping.ts queries.test.ts queries.ts rrf.test.ts rrf.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
shared/61
src/57
dto/17ai.dto.ts auth.dto.test.ts auth.dto.ts block.dto.ts browser-safety.test.ts comment.dto.ts database.dto.test.ts database.dto.ts file.dto.ts formula-ast.ts index.ts notification.dto.ts page.dto.ts search.dto.ts share.dto.ts webhook.dto.ts workspace.dto.ts
events/8audit-catalog.test.ts audit-catalog.ts audit-csv.test.ts audit-csv.ts audit-scope.test.ts audit-scope.ts catalog.test.ts catalog.ts
realtime/6comment-events.ts frames.test.ts frames.ts notification-policy.ts notification-prefs.ts task-0094.test.ts
automation.test.ts automation.ts constants.ts crypto.test.ts crypto.ts env.ts errors.ts events.test.ts events.ts forms.test.ts forms.ts index.ts markdown.test.ts markdown.ts mentions.test.ts mentions.ts net.test.ts net.ts positions.test.ts positions.ts presence.test.ts presence.ts security-headers.ts webhook-delivery.test.ts webhook-delivery.ts webhook-events.ts
package.json README.md tsconfig.json vitest.config.ts
storage/16
src/12client.test.ts client.ts index.ts keys.test.ts keys.ts mime.test.ts mime.ts presign.test.ts presign.ts thumbnails.test.ts thumbnails.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
ui/37
src/33
components/19avatar.tsx badge.tsx button.tsx checkbox.tsx command.tsx context-menu.tsx dialog.tsx dropdown-menu.tsx empty-state.tsx index.ts popover.tsx separator.tsx skeleton.tsx slider.tsx spinner.tsx switch.tsx tabs.tsx toast.tsx tooltip.tsx
color.ts command-filter.test.ts command-filter.ts focus.test.ts focus.ts formatters.test.ts formatters.ts index.ts theme.test.ts theme.ts toast-store.test.ts toast-store.ts tokens.test.ts tokens.ts
package.json README.md tsconfig.json vitest.config.ts
prisma/16
migrations/13
20260927000000_init/1migration.sql
20260928000000_search_documents_content_hash/1migration.sql
20260929000000_workspace_usage_storage_bytes/1migration.sql
20260930000000_form_submitted_notification_type/1migration.sql
20261001000000_ai_phase12/1migration.sql
20261002000000_webhooks_secret_encrypted_dlq/1migration.sql
20261002111436_webhook_delivery_latency/1migration.sql
20261002120000_org_admin_2fa_retention_unique/1migration.sql
20261002215124_audit_export/1migration.sql
20261003210000_perf_indexes_hot_paths/1migration.sql
20261004004500_perf_indexes_hot_paths_2/1migration.sql
migration_lock.toml README.md
schema.prisma seed.ts verify.ts
scripts/7backup-health.ts docs-completeness.mjs generate-openapi.mjs minio-init.ts opensearch-reindex.ts storage-orphans.ts verify-clean-clone.sh
tests/169
e2e/63
.auth/1user.json
fixtures/7
files/6clip.mp4 clip.wav clip.webm hero-1400.png note.docx note.txt
realtime.ts
mocks/2oidc-issuer.ts saml-idp.ts
playwright-report/3
data/2a6557bae9c2714a658968d4eb8534195c08ebda8.png e3092d07d00a16063e46385c02a559a9054fa847.webm
index.html
reports/1files-security.json
specs/34ai-database.spec.ts ai-generate.spec.ts ai-rag.spec.ts ai-security.spec.ts ai-writing.spec.ts ai.spec.ts api-keys.spec.ts appearance-theme.spec.ts auth.spec.ts automation.spec.ts collab-two-tab.spec.ts comments.spec.ts databases.spec.ts default-workspace-alias.spec.ts developers.spec.ts editor.spec.ts files.spec.ts final-flow.spec.ts forms.spec.ts oauth.spec.ts org-admin.spec.ts pages.spec.ts performance.spec.ts presence-cursors.spec.ts qa-paste-toggle.spec.ts realtime.spec.ts scim.spec.ts search-index-lag.spec.ts search.spec.ts sharing.spec.ts sso.spec.ts task-0094-comments-notifications.spec.ts webhooks.spec.ts workspace.spec.ts
test-results-final-flow/3
final-flow-Release-v1-0-fi-6c06b-w-state-carries-sequential--chromium-desktop/2test-failed-1.png trace.zip
.last-run.json
utils/4api.ts dnd.ts search-lag.ts wait.ts
.gitignore fixtures.ts global-setup.ts package.json playwright.config.ts playwright.final-flow.config.ts README.md tsconfig.json
fixtures/1
files/1make-sparse.ts
integration/60
ai-capability/2ai-app.capability.test.ts gateway.capability.test.ts
drill/2seed-drill.mjs verify-restore.mjs
fixtures/5
files/5clip.mp4 clip.wav doc.pdf hero-1400.png note.txt
helpers/3api.ts containers.ts seed.ts
realtime/11control-channel.test.ts convergence.test.ts flush-rebase-http.test.ts handshake.test.ts heartbeat-limits.test.ts lifecycle.test.ts presence.test.ts rebase.test.ts resume.test.ts rt-helpers.ts task-0094-fanout.test.ts
ai-chat.integration.test.ts ai-database.integration.test.ts ai-generate.integration.test.ts ai-rag.integration.test.ts ai-writing.integration.test.ts api-keys.integration.test.ts audit-completeness.integration.test.ts audit-export-s3.integration.test.ts auth.integration.test.ts automation-pipeline.integration.test.ts automations.integration.test.ts blocks.integration.test.ts cache-invalidation.integration.test.ts comments.integration.test.ts databases.integration.test.ts files-lifecycle.integration.test.ts files-pipeline.integration.test.ts files-security.integration.test.ts files.integration.test.ts forms-responses.integration.test.ts forms.integration.test.ts oauth.integration.test.ts openapi.contract.test.ts org-admin.integration.test.ts package.json pages.integration.test.ts permissions.integration.test.ts README.md replica-routing.integration.test.ts retention-engine.integration.test.ts scim.integration.test.ts search.integration.test.ts sso.integration.test.ts tsconfig.json vitest.config.ts webhooks.integration.test.ts workspace.integration.test.ts
performance/28
fixtures/1search-100k.ts
helpers/2seed-100k.mjs seed-1k-load.mjs
reports/7api-load-smoke.json database-load-smoke.json editor-local.json realtime-load.json search-full.json search-last.json search-reduced.json
api-load.js api.js archive.tar database-load.js database.js editor-local.mjs extract-100k-env.mjs package.json README.md realtime-1k.js realtime-load.js realtime.js run-realtime-load.sh search-search.k6.js search-summary.json seed-100k.mjs threshold-breach-demo.js verify-zero-op-loss.mjs
security/16
helpers/1app.ts
audit-report.test.ts auth-paths.test.ts deps.test.ts formula-fuzz.test.ts gitleaks-config.test.ts headers.test.ts images.test.ts oracle.ts package.json permission-fuzz.test.ts rate-limits.test.ts README.md tsconfig.json upload-abuse.test.ts vitest.config.ts
README.md
.dockerignore .env .env.bak-task0125 .env.example .eslintrc.json .gitignore .npmrc .prettierignore .prettierrc.json AGENTS.md ARCHITECTURE.md CHANGELOG.md CONTRIBUTING.md gitleaks.toml LICENSE package.json pnpm-lock.yaml pnpm-workspace.yaml PRODUCT-SPEC.md README.md ROADMAP.md SECURITY.md tsconfig.json turbo.json
authentication.mddocs/api/authentication.md← api35,421 B← Back to the app

Authentication & API Conventions

This file is the canonical reference for API-wide conventions: base URL, authentication, the standard error model and error-code table, pagination, rate limiting, IDs, and timestamps. Every other API document (workspaces, pages, blocks, databases, comments, files, search, webhooks) links back here instead of repeating these rules.

Base URL for all endpoints:

https://<host>/api/v1

In local development the API serves at http://localhost:4000/api/v1.


1. Authentication Overview

AI Software Factory is a multi-tenant platform: the workspace is the top-level tenant, and every tenant-scoped resource (pages, databases, files, comments, webhooks, …) is owned by exactly one workspace. All read/write endpoints operate within a workspace; the API infers it in this order:

  1. The path parameter :workspaceId (workspace-scoped routes).
  2. The X-Factory-Workspace header (any workspace the caller belongs to).
  3. For user-scoped routes, the caller's personal workspace.

Authentication is always resolved server-side. Three credential types are accepted, all sent in the Authorization header:

Credential Header Scope Lifetime
JWT access token Authorization: Bearer eyJhbGciOi… User session 15 minutes
API key Authorization: Bearer fk_live_… Workspace (per-key scopes: READ / WRITE / ADMIN) Until revoked or expired
OAuth2 (for third-party apps) Authorization: Bearer eyJhbGciOi… (app-issued JWT) App + user grant 15 minutes (refreshable via OAuth2 refresh token)

Every response to an authenticated request carries an X-Request-Id header that matches the requestId field in error bodies. Use it when reporting issues.

1.1 Access Token Format

Access tokens are signed JWTs (HS256 by default, RS256 in enterprise deployments). Payload:

{
  "sub": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A",
  "workspaceId": "wks_01J9ZKP2B7F4H6J8K0L2M4N6P8",
  "roles": ["MEMBER"],
  "scopes": ["read", "write"],
  "jti": "at_01J9ZKR1C8G5I7J9L1M3N5P7Q9",
  "iat": 1735689600,
  "exp": 1735690500
}

1.2 Refresh Token Rotation

Refresh tokens are opaque 256-bit random strings (e.g. rft_7f9c2e…41 chars), stored hashed (SHA-256) in the refresh_tokens table, and rotated on every use: each refresh returns a new refresh token and invalidates the one that was presented.

┌──────────┐  POST /auth/login   ┌────────────┐
│  Client  │ ──────────────────► │   API      │
│          │ ◄────────────────── │            │
│          │  { accessToken,     │            │
│          │    refreshToken: R1 }│            │
└──────────┘                     └────────────┘
      │                                 │
      │  access token expires (15 min)  │
      ▼                                 │
┌──────────┐  POST /auth/refresh ┌──────┴─────┐
│  Client  │  { refreshToken: R1 }│   API      │
│          │ ──────────────────► │ verify R1  │
│          │ ◄────────────────── │ rotate:    │
│          │  { accessToken,     │  R1 → R2   │
│          │    refreshToken: R2 }│            │
└──────────┘                     └────────────┘

Rotation rules:


2. Auth Endpoints

2.1 POST /auth/register

Create a user account. A personal workspace is created automatically and an email-verification token is emailed (see §6 Email Verification).

Request:

{
  "email": "ada@example.com",
  "password": "c0rrect-Horse-battery9",
  "name": "Ada Lovelace"
}
Field Type Required Notes
email string (email) yes Lower-cased and unique.
password string yes 8–128 chars; must satisfy the configured strength policy (upper, lower, digit).
name string (1–100) yes Full name; displayName defaults to it.

201 Created

{
  "data": {
    "user": {
      "id": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A",
      "email": "ada@example.com",
      "name": "Ada Lovelace",
      "displayName": "Ada Lovelace",
      "avatarUrl": null,
      "emailVerifiedAt": null,
      "emailVerification": "PENDING",
      "status": "ACTIVE",
      "locale": "en-US",
      "timezone": "UTC",
      "createdAt": "2025-01-01T10:00:00.000Z"
    },
    "workspace": {
      "id": "wks_01J9ZKP2B7F4H6J8K0L2M4N6P8",
      "name": "Ada Lovelace's Workspace",
      "slug": "ada-lovelace-7f3k9q",
      "isPersonal": true,
      "ownerId": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A"
    },
    "accessToken": "eyJhbGciOiJIUzI1NiIs…",
    "refreshToken": "rft_8f14e45fceea167a5a36dedd4bea2543…",
    "session": {
      "id": "ses_01J9ZKQ9D0G7I9K1L3M5N7P9Q1R3",
      "expiresAt": "2025-01-30T10:00:00.000Z"
    }
  }
}

Errors

Status Code When
400 VALIDATION_ERROR Bad email, weak password, missing fields (see details).
409 CONFLICT Email already registered.
429 RATE_LIMITED Registration limit exceeded.

Events emitted: user.registered, workspace.created (personal).


2.2 POST /auth/login

Exchange email + password for a token pair.

Request:

{
  "email": "ada@example.com",
  "password": "c0rrect-Horse-battery9"
}

200 OK

{
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs…",
    "refreshToken": "rft_1c383cd30bfaee4c945754e37e86a074…",
    "expiresIn": 900,
    "session": {
      "id": "ses_01J9ZKR3E2H8J0L2M4N6P8Q0R2S4",
      "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)…",
      "ipAddress": "203.0.113.7",
      "expiresAt": "2025-01-30T10:00:00.000Z"
    },
    "user": {
      "id": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A",
      "email": "ada@example.com",
      "emailVerified": true,
      "status": "ACTIVE"
    }
  }
}

Errors

Status Code When
400 VALIDATION_ERROR Missing fields.
401 UNAUTHORIZED Unknown email or wrong password (message is deliberately identical for both).
403 FORBIDDEN Account suspended (details: [{reason: "account_suspended"}]).
429 RATE_LIMITED Too many attempts; details includes retryAfterSeconds.

Events emitted: user.login (audit-logged: actor, IP, user agent).


2.3 POST /auth/refresh

Rotate the refresh token and receive a fresh access token.

Request:

{
  "refreshToken": "rft_1c383cd30bfaee4c945754e37e86a074…"
}

200 OK

{
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs…",
    "refreshToken": "rft_9d0b5a1e2f3c4d5e6f7a8b9c0d1e2f3a…",
    "expiresIn": 900,
    "session": { "id": "ses_01J9ZKR3E2H8J0L2M4N6P8Q0R2S4" }
  }
}

Errors

Status Code When
401 UNAUTHORIZED Token unknown or expired.
401 REFRESH_TOKEN_REUSED Token already rotated → whole session revoked.
403 FORBIDDEN Session revoked or user suspended.

Events emitted: auth.refresh (audit).


2.4 POST /auth/logout

Revoke the refresh token that issued the given access token (or the session named in the body, if provided). Subsequent refreshes with the rotated token fail. Idempotent.

Request (both fields optional):

{
  "sessionId": "ses_01J9ZKR3E2H8J0L2M4N6P8Q0R2S4"
}

204 No Content

Errors: 401 UNAUTHORIZED (expired/invalid access token).

Events emitted: user.logout (audit).


2.5 POST /auth/email-verification/request

Re-send the email verification link (or issue a new token for an unverified account).

Request:

{ "email": "ada@example.com" }

202 Accepted — always 202 with an empty envelope so the endpoint cannot be used to enumerate accounts:

{ "data": { "sent": true } }

The verification email contains a link https://<host>/verify-email?token=evt_… (single-use, 24 h).

Errors: 401 UNAUTHORIZED, 429 RATE_LIMITED.


2.6 POST /auth/email-verification/confirm

Confirm the email address with the token from the verification link.

Request:

{ "token": "evt_5b1e0c3d9f2a4b6c8d0e1f2a3b4c5d6e" }

200 OK

{
  "data": {
    "user": {
      "id": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A",
      "email": "ada@example.com",
      "emailVerification": "VERIFIED",
      "emailVerifiedAt": "2025-01-01T10:05:00.000Z"
    }
  }
}

Errors: 400 VALIDATION_ERROR (malformed token), 410 GONE (token expired), 409 CONFLICT (token already used — reported as already-verified).


3. Password Reset Flow

POST /auth/password-reset/request   ──► email with 24 h single-use token
POST /auth/password-reset/confirm   ──► new password, all sessions revoked

3.1 POST /auth/password-reset/request

Request:

{ "email": "ada@example.com" }

202 Accepted — always succeeds (no account enumeration):

{ "data": { "sent": true } }

The email links to https://<host>/reset-password?token=pwrt_….

Errors: 400 VALIDATION_ERROR, 429 RATE_LIMITED.


3.2 POST /auth/password-reset/confirm

Request:

{
  "token": "pwrt_c9f2a7b4e1d83056f7a9b2c4d6e8f0a1",
  "password": "n3w-C0rrect-Horse-battery"
}

200 OK

{ "data": { "reset": true } }

All active sessions and refresh tokens for the user are revoked; the client must log in again.

Errors

Status Code When
400 VALIDATION_ERROR Weak password, malformed token.
410 GONE Token expired.
409 CONFLICT Token already used or revoked.

Events emitted: user.password_reset (audit, all sessions revoked).


4. Current User

4.1 GET /users/me

200 OK

{
  "data": {
    "user": {
      "id": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A",
      "email": "ada@example.com",
      "name": "Ada Lovelace",
      "displayName": "Ada",
      "avatarUrl": "https://<host>/files/fil_01J9ZKS…/avatar.png",
      "emailVerification": "VERIFIED",
      "emailVerifiedAt": "2025-01-01T10:05:00.000Z",
      "status": "ACTIVE",
      "locale": "en-US",
      "timezone": "Europe/London",
      "theme": "dark",
      "lastLoginAt": "2025-01-02T08:12:00.000Z",
      "createdAt": "2024-11-03T09:00:00.000Z"
    },
    "workspaces": [
      { "id": "wks_01J9ZKP2B7F4H6J8K0L2M4N6P8", "name": "Ada Lovelace's Workspace", "isPersonal": true, "role": "OWNER" },
      { "id": "wks_01J9ZKP5C9H7J9K1L3M5N7P9Q1", "name": "Analytical Engines Co.", "isPersonal": false, "role": "ADMIN" }
    ]
  }
}

Errors: 401 UNAUTHORIZED.


4.2 PATCH /users/me

Update profile and preferences. Only the fields present in the body are changed.

Request:

{
  "name": "Ada K. Lovelace",
  "displayName": "Ada",
  "avatarUrl": "https://<host>/files/fil_01J9ZKS…/avatar.png",
  "locale": "en-GB",
  "timezone": "Europe/London",
  "theme": "dark"
}

200 OK — returns the updated user object (same shape as GET /users/me data.user).

Errors: 400 VALIDATION_ERROR, 401 UNAUTHORIZED, 409 CONFLICT (e.g. name length violation is VALIDATION_ERROR; a second user with the same displayName in a workspace is CONFLICT with details: [{field: "displayName"}]).

Events emitted: user.profile_updated.


5. Session Management

A session is a long-lived login (30 days of inactivity) that owns a refresh token rotation chain. Sessions can be listed and revoked — revoking a session revokes every refresh token in its chain.

5.1 GET /sessions

Query params:

Param Type Default Notes
cursor string – Opaque cursor from nextCursor.
limit int (1–100) 20 Page size.

200 OK

{
  "data": [
    {
      "id": "ses_01J9ZKR3E2H8J0L2M4N6P8Q0R2S4",
      "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/121",
      "ipAddress": "203.0.113.7",
      "status": "ACTIVE",
      "isCurrent": true,
      "lastActiveAt": "2025-01-02T09:55:00.000Z",
      "createdAt": "2025-01-02T08:12:00.000Z",
      "expiresAt": "2025-01-30T09:55:00.000Z"
    },
    {
      "id": "ses_01J9ZKQ9D0G7I9K1L3M5N7P9Q1R3",
      "userAgent": "factory-api-client/1.0 (curl/8.4.0)",
      "ipAddress": "203.0.113.9",
      "status": "ACTIVE",
      "isCurrent": false,
      "lastActiveAt": "2025-01-02T07:03:00.000Z",
      "createdAt": "2025-01-01T10:00:00.000Z",
      "expiresAt": "2025-01-29T07:03:00.000Z"
    }
  ],
  "nextCursor": "c2VzXzAxSlpaS1E5RDBHNUk5SzFMMy"
}

Errors: 401 UNAUTHORIZED.


5.2 DELETE /sessions/:sessionId

Revoke a session. The session id of the current session can be revoked too (its refresh chain dies, but the access token lives until its 15-min expiry). Idempotent: deleting an already-revoked session returns 204.

Path params:

Param Type Notes
sessionId string Session id (ses_…).

204 No Content

Errors

Status Code When
401 UNAUTHORIZED Missing/invalid access token.
404 NOT_FOUND Session does not exist (or belongs to another user).

Events emitted: session.revoked (audit, actor + target session recorded).


6. Email Verification

See §2.5 and §2.6. Summary of the flow:

register ──► email (token evt_…, 24 h, single use)
                │
                ▼
        confirm ──► user.emailVerification = VERIFIED

7. API Keys

API keys authenticate service-to-service calls on behalf of a workspace. They are created in workspace settings; the raw key is shown exactly once at creation (only a hash is stored server-side).

Usage — identical to JWT usage, just a different token in the same header:

curl -s https://factory.example.com/api/v1/pages \
  -H "Authorization: Bearer fk_live_aB3dE5fG7hI9jK1lM3nO5pQ7rS9t" \
  -H "X-Factory-Workspace: wks_01J9ZKP5C9H7J9K1L3M5N7P9Q1"

Key management (workspace-scoped, ADMIN scope or workspace Owner/Admin role):

7.1 POST /workspaces/:workspaceId/api-keys

Request:

{
  "name": "CI pipeline",
  "scopes": ["READ", "WRITE"],
  "rateLimit": 600,
  "expiresAt": "2025-07-01T00:00:00.000Z"
}

201 Created — key appears only in this response:

{
  "data": {
    "id": "apk_01J9ZKS7F2H9J1L3M5N7P9Q1R3S5",
    "name": "CI pipeline",
    "key": "fk_live_aB3dE5fG7hI9jK1lM3nO5pQ7rS9tUvWxYz",
    "prefix": "fk_live_aB3d",
    "scopes": ["READ", "WRITE"],
    "rateLimit": 600,
    "expiresAt": "2025-07-01T00:00:00.000Z",
    "createdById": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A",
    "createdAt": "2025-01-02T10:00:00.000Z"
  }
}

Errors: 400 VALIDATION_ERROR, 403 FORBIDDEN, 404 NOT_FOUND, 409 CONFLICT (duplicate name is not an error — names are unique per workspace, so this returns CONFLICT).

Events emitted: api_key.created (audit, actor + key name; the raw key is never written to the audit log).

7.2 GET /workspaces/:workspaceId/api-keys

Returns { data: [ {id, name, prefix, scopes, rateLimit, lastUsedAt, expiresAt, revokedAt, createdById, createdAt} ] } — never the raw key.

7.3 DELETE /workspaces/:workspaceId/api-keys/:apiKeyId

Events emitted: api_key.revoked (audit).


8. OAuth2 (Authorization Code)

Third-party applications can obtain access to a user's workspaces via OAuth2 authorization code flow with PKCE (mandatory for public clients).

Registered clients carry client_id, a redirect_uris allow-list, and a grant type of authorization_code (+ refresh_token).

8.1 Flow

1. App redirects user:
   https://<host>/oauth/authorize?
     response_type=code
     &client_id=cli_01J9ZKT…
     &redirect_uri=https://app.example.com/callback
     &scope=pages:read databases:write
     &state=xyz123
     &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
     &code_challenge_method=S256

2. User logs in (if needed) and approves.
   Browser is redirected:
   https://app.example.com/callback?code=oc_…&state=xyz123

3. App exchanges the code:
   POST /api/v1/oauth/token
   Authorization: Basic base64(client_id:client_secret)   (confidential clients)
   { "grant_type": "authorization_code",
     "code": "oc_…",
     "redirect_uri": "https://app.example.com/callback",
     "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" }

4. API responds:
   { "access_token": "eyJ…", "token_type": "Bearer", "expires_in": 900,
     "refresh_token": "rft_…", "scope": "pages:read databases:write" }

5. Later: refresh with grant_type=refresh_token (rotation, same rules as §1.2).

Scope catalog (the only values a request may grant; apps offer a subset): pages:read, pages:write, databases:read, databases:write, files:read, files:write — resource:action, where <resource>:read grants VIEW on that resource and <resource>:write grants EDIT. There is deliberately no scope that reaches FULL_ACCESS (owner/admin-only routes stay closed to OAuth tokens; see C-13.7d in docs/tasks/phase-13-api.md).

8.2 Endpoints

Endpoint Purpose
GET /oauth/authorize Browser redirect; renders consent screen (login first if anonymous). 200 HTML, or 302 to redirect_uri with code + state on approval, error=access_denied on decline.
POST /oauth/token Token endpoint (JSON). Supports authorization_code and refresh_token grants.
POST /oauth/revoke {"token": "…", "token_type_hint": "refresh_token"} → 200 (RFC 7009; always 200, even for unknown tokens).
GET /oauth/clients/:clientId Introspection of the client's public metadata (name, logo, scopes offered).

Token endpoint errors (JSON, same canonical envelope as other errors — { error: { code, message, details?, requestId } }):

Status code Meaning
400 INVALID_GRANT Code expired/used, wrong PKCE verifier, or a refresh token that is dead (rotated, revoked, or family-killed).
400 INVALID_CLIENT Client authentication failed — client_id mismatch, or a confidential client with a missing or wrong client_secret.
400 INVALID_SCOPE scope contains a value the client does not offer.

Refresh rotation + reuse detection (same family model as §1.2, applied to the OAuth token family): every grant_type=refresh_token issues a new refresh token and invalidates the presented one. A presentation of an already rotated (dead) refresh token is treated as a token-reuse attack: the whole token family is revoked (the attacker and the victim's live token both die) and oauth.token_revoked is audited with reason: "reuse_detected". Note this is intentionally STRICTER than §1.2's login-session nuance (which distinguishes a benign stale retry with a live successor); the OAuth contract specifies plain family-kill on old-token reuse. See C-13.7e.

Issued JWTs contain client_id and the granted scope instead of a fixed workspaceId (plus type:"oauth" and a jti used for per-token revocation); each request must carry X-Factory-Workspace, validated against the user's memberships and the scope grant. Scope → permission cap: any *:write scope → EDIT, only *:read → VIEW, none → NONE (there is no scope that reaches FULL_ACCESS, so owner/admin-only routes stay closed to OAuth tokens).

Events emitted: oauth.app_created, oauth.app_updated, oauth.app_deleted, oauth.code_issued, oauth.token_issued, oauth.token_revoked (audit).

8.3 App registration

A workspace's OWNER or ADMIN registers third-party OAuth apps through the workspace-scoped Integration surface (stored as integrations.config.oauthApp for provider: "oauth"). The client_secret is returned exactly once (on create) and stored only as a SHA-256 hash; it is never included in list/get responses. Public clients ("public": true) have no secret and are required to use PKCE at authorize time.

Endpoint Purpose
POST /workspaces/:workspaceId/oauth/apps Create an app. Body: { name, redirectUris[], scopes[], logoUrl?, public? } → 201 with { id, clientId, clientSecret?, name, public, redirectUris, scopes, createdAt } (clientSecret present only for confidential clients).
GET /workspaces/:workspaceId/oauth/apps List the workspace's apps ({ data: [ … ] }, no secrets).
PATCH /workspaces/:workspaceId/oauth/apps/:clientId Update name / logoUrl / redirectUris / scopes.
DELETE /workspaces/:workspaceId/oauth/apps/:clientId Delete the app and drain all of its in-flight authorization codes and refresh tokens (families revoked, reason: "app_deleted"). → 200 { deleted: true, clientId }.

All four are role-gated to OWNER/ADMIN (a MEMBER gets 403 { reason: "oauth_role_denied" } — and an OAuth token can never manage apps since it cannot reach FULL_ACCESS; see C-13.7d). redirectUris entries must be absolute http(s) URLs and are matched exactly at authorize time. scopes are filtered to the six-scope catalog (pages:read|write, databases:read|write, files:read|write); at least one valid scope is required.


9. Standard Error Model (Canonical)

Every non-2xx response from the API uses the same envelope, produced by the typed ApiError class:

{
  "error": {
    "code": "PERMISSION_DENIED",
    "message": "You do not have EDIT access to this page.",
    "details": [
      { "field": "permission", "issue": "requires EDIT level, you have VIEW" }
    ],
    "requestId": "req_01J9ZKV2A8B4C6D8E0F2G4H6J8"
  }
}
Field Type Notes
code string Stable machine-readable code (table below). Never localize.
message string Human-readable, safe to surface in UIs.
details array (optional) Field-level or contextual info. For validation errors: one entry per failed constraint (field, issue, received).
requestId string Matches the X-Request-Id response header; include in support reports.

9.1 Error Code Table (THE canonical table)

Code HTTP Meaning Typical details
VALIDATION_ERROR 400 Failed request validation (Zod). [{field, issue, received}] per constraint.
UNAUTHORIZED 401 Missing, expired, or invalid credentials (JWT, refresh token, API key). [{reason: "token_expired" | "token_malformed" | "refresh_token_reused" | "api_key_revoked" | "api_key_expired"}]
AI_BUDGET_EXCEEDED 402 The workspace's monthly AI token budget is exhausted (AI endpoints only). 402 signals "payment/budget required" — distinct from RATE_LIMITED 429. [{reason: "monthly_token_budget_exceeded", used, limit}]
FORBIDDEN 403 Authenticated, but not permitted (RBAC/resource level, scope, wrong credential type). [{reason: "requires_EDIT" | "requires_user_credentials" | "api_key_scope_write" | "account_suspended"}]
NOT_FOUND 404 Resource does not exist or is not visible to you (never leak existence across permission boundaries). —
CONFLICT 409 State conflict: duplicate unique value, stale version (optimistic concurrency), invitation already accepted, token already used. [{field, issue: "already_exists" | "version_mismatch", expected, received}]
PRECONDITION_FAILED 412 If-Match / version check failed on an update. [{expectedVersion, actualVersion}]
GONE 410 Resource existed but is permanently gone (expired one-time token). [{reason: "token_expired"}]
UNPROCESSABLE_ENTITY 422 Syntactically valid but semantically invalid (e.g. filter tree references a deleted property, circular page nesting). [{issue, resource}]
FORMULA_TIMEOUT 422 A syntactically valid formula exceeded the evaluation budget: the AST evaluator's 100 000-op limit (EVAL_OP_LIMIT) or the 50 ms cooperative wall-clock backstop (EVAL_TIMEOUT_MS). Distinct from VALIDATION_ERROR/UNPROCESSABLE_ENTITY (syntax/semantics) — this is a resource-guard rejection (TASK-0134; the table is extensible by design). [{reason: "formula_budget", opLimit: 100000, timeoutMs: 50}]
RATE_LIMITED 429 Too many requests (see §11 Rate Limiting). [{retryAfterSeconds}]
PAYLOAD_TOO_LARGE 413 Body exceeds the per-route limit (e.g. block batch > 1 MB). [{maxBytes}]
METHOD_NOT_ALLOWED 405 Known path, wrong verb. [{allowedMethods: ["GET", "PATCH"]}]
INTERNAL_ERROR 500 Unhandled server error. —
SERVICE_UNAVAILABLE 503 Dependency down (OpenSearch, object storage, Redis) or workspace in degraded state. [{service: "opensearch"}]
NOT_IMPLEMENTED 501 Endpoint not enabled in this deployment/plan. [{feature}]

Conventions:

9.2 Validation Error Example

POST /api/v1/pages with a bad body:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed.",
    "details": [
      { "field": "title", "issue": "string too short (min 0, received NaN)" },
      { "field": "parentId", "issue": "invalid cuid" }
    ],
    "requestId": "req_01J9ZKX5B0C2D4E6F8G0H2I4J6"
  }
}

10. Pagination (Cursor-Based, All List Endpoints)

All list endpoints use the same cursor scheme.

Request:

Param Type Default Notes
cursor string – Opaque base64 cursor from the previous page's nextCursor. Never parse it.
limit int 20 Clamped per-endpoint (see each doc; max 100 for most, 200 for search).

Response envelope (identical everywhere):

{
  "data": [ "…" ],
  "nextCursor": "c2VzXzAxSlpaS1E5RDBHNUk5SzFMMy"
}

11. Rate Limiting

Rate limits are applied per (credential, IP) using a Redis sliding window. All buckets below are enforced server-side; the exact defaults are pinned in tests/security/rate-limits.test.ts (a change to a limit without a new finding + justification fails CI).

Bucket Key Default limit Notes
Per credential / IP (API) rl:api:<user|ip> 100 req/min (RATE_LIMIT_API) The generic window for authenticated (user) and anonymous (IP) API calls.
Auth endpoints (login / register / reset) rl:auth:<user|ip> 10 req/min (RATE_LIMIT_AUTH) The tighter bucket for the credential endpoints.
Per API key window per key 600 req/min (RATE_LIMIT_API_KEY), or the key's own rateLimit if set The key's rateLimit always wins (set at key creation); applies on top of the per-credential window.
Incoming webhook (per webhook) auto:webhook-rate:<webhookId> 60 req/min (WEBHOOK_RATE_PER_MINUTE) New in TASK-0134. Enforced on POST /api/v1/webhooks/incoming/:webhookId before signature verification (rate-limited callers never reach signature work). 429 body carries details[0].retryAfterSeconds: 60.
Public form submission (per IP + form) form:rl:{slug}:{ip} 5 req/min (FORM_SUBMIT_LIMIT, 60 s window) New in TASK-0134. Enforced on POST /api/v1/public/forms/:slug/submit per (IP, form). 429 returns the Retry-After: 60 header + details[0].retryAfterSeconds: 60.

Spec delta (recorded, AGENTS rule 29): the task spec's example for public-form is 20/min/IP+form; this release ships the stricter 5/min (a public form accepting more than a handful of submissions per minute per IP is an abuse signal). The incoming-webhook bucket matches the spec (60/min). Weakening either value requires a new finding + justification in docs/security/audit-report.md.

Deployment note: RATE_LIMIT_API / RATE_LIMIT_AUTH are deployment tunables (the dev .env raises them for load testing; CI raises them so the other test suites don't trip them). The two new buckets are NOT env tunables — they are fixed platform limits (a public webhook/form endpoint must be bounded regardless of deployment), which is what makes them live-testable at their documented value in CI.

Headers are returned on every response:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1735690500          # unix seconds when the window resets
Retry-After: 32                          # only present on 429 responses

Exceeding a bucket returns 429 RATE_LIMITED with details: [{retryAfterSeconds: 32}] and the Retry-After header (the incoming-webhook 429 uses WEBHOOK_RATE_LIMITED; the public-form 429 uses RATE_LIMITED + the Retry-After header).

Best practice: when you receive 429, stop, wait Retry-After seconds (jitter ±10%), then resume with linear backoff up to 5 s.


12. IDs & Timestamps


13. Quick Reference: curl

# Register
curl -s -X POST https://factory.example.com/api/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"email":"ada@example.com","password":"c0rrect-Horse-battery9","name":"Ada Lovelace"}'

# Login
curl -s -X POST https://factory.example.com/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"ada@example.com","password":"c0rrect-Horse-battery9"}'

# Authenticated call (JWT)
curl -s https://factory.example.com/api/v1/users/me \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Refresh (rotation)
curl -s -X POST https://factory.example.com/api/v1/auth/refresh \
  -H 'Content-Type: application/json' \
  -d "{\"refreshToken\":\"$REFRESH_TOKEN\"}"

# Authenticated call (API key)
curl -s https://factory.example.com/api/v1/pages \
  -H "Authorization: Bearer fk_live_aB3dE5fG7hI9jK1lM3nO5pQ7rS9tUvWxYz" \
  -H "X-Factory-Workspace: wks_01J9ZKP5C9H7J9K1L3M5N7P9Q1"

# Revoke session
curl -s -X DELETE https://factory.example.com/api/v1/sessions/ses_01J9ZKQ9D0G7I9K1L3M5N7P9Q1R3 \
  -H "Authorization: Bearer $ACCESS_TOKEN" -o /dev/null -w "%{http_code}\n"