.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
databases.mddocs/api/databases.md← api38,148 B← Back to the app

Databases API

Databases are typed, queryable collections of records. In AI Software Factory every record is also a page (database_records.pageId is 1:1 with pages.id): each row opens a full page with its own blocks, while its property values live in the database. Databases are embedded in pages via database / linked_database / child_database blocks or exist standalone in the workspace.

Shared conventions (auth, error model, pagination, rate limits): authentication.md.

Permission note: reading a database requires VIEW; creating/updating records and the schema requires EDIT; archiving a database requires FULL_ACCESS (or workspace Admin/Owner). All checks are server-side and inherit the workspace → team → page chain.

Scale note: databases are designed for 100,000+ records. List and query endpoints are always cursor-paginated; the engine evaluates filter/sort/group/aggregates server-side, so clients never fetch a whole database.


1. Database Resource

{
  "id": "db_01J9ZKQ7E4F6A8B0C2D4E6F8A0",
  "workspaceId": "wks_01J9ZKP5C9H7J9K1L3M5N7P9Q1",
  "pageId": "pg_01J9ZKQ4F8H5I7J9L1M3N5P7Q9",
  "title": "Projects",
  "icon": "📊",
  "description": "All projects, owners, status, and budgets.",
  "schemaVersion": 4,
  "isArchived": false,
  "recordCount": 128,
  "createdBy": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A",
  "createdAt": "2025-01-02T09:20:00.000Z",
  "updatedAt": "2025-01-02T16:02:00.000Z"
}

2. Create a Database

POST /databases

Request:

{
  "title": "Projects",
  "icon": "📊",
  "description": "All projects, owners, status, and budgets.",
  "pageId": "pg_01J9ZKQ4F8H5I7J9L1M3N5P7Q9",
  "blockId": "blk_01J9ZKQ6D3E5F7A9B1C3D5E7F9",
  "parentId": null,
  "properties": [
    { "name": "Name", "type": "title" },
    { "name": "Status", "type": "select", "config": { "options": [
        { "id": "opt_01J9ZKQ8A1B3C5D7E9F1A3B5C7", "name": "Not Started", "color": "gray" },
        { "id": "opt_01J9ZKQ8B2C4D6E8F0A2B4C6D8", "name": "In Progress", "color": "blue" },
        { "id": "opt_01J9ZKQ8C3D5E7F9A1B3C5D7E9", "name": "Done", "color": "green" } ] } },
    { "name": "Owner", "type": "person" },
    { "name": "Priority", "type": "select", "config": { "options": [
        { "id": "opt_01J9ZKQ8D4E6F8A0B2C4D6E8F0", "name": "High", "color": "red" },
        { "id": "opt_01J9ZKQ8E5F7A9B1C3D5E7F9A1", "name": "Medium", "color": "yellow" },
        { "id": "opt_01J9ZKQ8F6A8B0C2D4E6F8A0B2", "name": "Low", "color": "gray" } ] } },
    { "name": "Due Date", "type": "date" },
    { "name": "Tags", "type": "multi_select", "config": { "options": [
        { "id": "opt_01J9ZKQ9A7B9C1D3E5F7A9B1C3", "name": "platform", "color": "purple" },
        { "id": "opt_01J9ZKQ9B8C0D2E4F6A8B0C2D4", "name": "customer", "color": "orange" } ] } },
    { "name": "Budget", "type": "number", "config": { "format": "currency", "currency": "USD" } }
  ],
  "views": [
    { "name": "Table view", "type": "table" },
    { "name": "Board", "type": "board", "group": [ { "propertyId": "Status", "direction": "asc" } ] }
  ]
}

Note: when referencing a property you are creating in the same request (e.g. a group by Status), use the name in propertyId — the server resolves names of properties defined in the same payload. For already- existing properties use the prop_… id.

Field Type Required Notes
title string (0–200) no Empty allowed.
icon, description – no As for pages.
pageId string | null no Page to embed the database block into (a database block is created/linked).
blockId string | null no With pageId: the existing database block to bind (created by slash command); the API then fills its content.databaseId.
parentId string | null no Standalone placement (parent page in the workspace tree). Mutually exclusive with pageId+blockId.
properties array (1–200) no Default: a single title property named "Name". See §3 Property Types.
views array (0–20) no Default: one table view. See §5 Views.

Exactly one of pageId or parentId (or neither → workspace root).

201 Created

{
  "data": {
    "id": "db_01J9ZKQ7E4F6A8B0C2D4E6F8A0",
    "workspaceId": "wks_01J9ZKP5C9H7J9K1L3M5N7P9Q1",
    "pageId": "pg_01J9ZKQ4F8H5I7J9L1M3N5P7Q9",
    "title": "Projects",
    "icon": "📊",
    "description": "All projects, owners, status, and budgets.",
    "schemaVersion": 1,
    "isArchived": false,
    "recordCount": 0,
    "properties": [
      { "id": "prop_01J9ZKQ7F5A7B9C1D3E5F7A9B1", "name": "Name", "type": "title", "position": "a0", "isTitle": true, "isVisible": true, "required": false, "config": {} },
      { "id": "prop_01J9ZKQ7G6B8C0D2E4F6A8B0C2", "name": "Status", "type": "select", "position": "a1", "isTitle": false, "isVisible": true, "required": false, "config": { "options": [ "…as in request…" ] } }
    ],
    "views": [
      { "id": "view_01J9ZKQ7H7C9D1E3F5A7B9C1D3", "name": "Table view", "type": "table", "position": "a0", "config": {}, "filter": null, "sort": null, "group": null, "pageSize": null }
    ],
    "createdBy": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A",
    "createdAt": "2025-01-02T09:20:00.000Z"
  }
}

Errors: 400 VALIDATION_ERROR (bad property config, duplicate property name, no title property), 401 UNAUTHORIZED, 403 FORBIDDEN, 404 NOT_FOUND (page), 409 CONFLICT (two properties named the same after case folding).

Events emitted: database.created (+ page.updated if embedded — the database block's content.databaseId is set).


3. Properties

A database has an ordered list of typed properties (database_properties). Exactly one property may have isTitle: true.

3.1 GET /databases/:databaseId

200 OK — database object plus properties (ordered by position) and views. Same shape as the 201 above.

Errors: 401 UNAUTHORIZED, 403 FORBIDDEN, 404 NOT_FOUND.


3.2 PATCH /databases/:databaseId

Update title/icon/description or archive.

Request (any subset):

{
  "title": "Projects 2025",
  "icon": "🗂️",
  "archived": false
}

200 OK — updated database object.

Errors: 400 VALIDATION_ERROR, 403 FORBIDDEN, 404 NOT_FOUND, 409 CONFLICT (archived: true requires FULL_ACCESS, else 403).

Events emitted: database.updated.


3.3 Property CRUD

Properties are managed per-database.

POST /databases/:databaseId/properties

Request:

{
  "name": "Start Date",
  "type": "date",
  "position": "a0",
  "isVisible": true,
  "required": false,
  "config": { "includeTime": false },
  "default": null
}

201 Created — the property object:

{
  "data": {
    "id": "prop_01J9ZKQ9C9D1E3F5A7B9C1D3E5",
    "databaseId": "db_01J9ZKQ7E4F6A8B0C2D4E6F8A0",
    "name": "Start Date",
    "type": "date",
    "position": "a0",
    "isTitle": false,
    "isVisible": true,
    "required": false,
    "config": { "includeTime": false },
    "default": null,
    "createdAt": "2025-01-02T10:00:00.000Z"
  }
}

Errors: 400 VALIDATION_ERROR (bad config for type), 403 FORBIDDEN, 404 NOT_FOUND, 409 CONFLICT (duplicate name).

Events emitted: database.updated (schema change; metadata: {change: "property_added"}).

GET /databases/:databaseId/properties

200 OK — { data: [ <property objects> ] }, ordered by position. No pagination (property counts are bounded by the 200 cap).

PATCH /databases/:databaseId/properties/:propertyId

Update name, isVisible, required, position (reorder), and config (merge-patch; for select-like types you may add or rename options — removing an option clears that value from every record that uses it, and the response reports affectedRecords). type is immutable (delete + recreate to change type); isTitle is immutable (use POST /databases/:id/properties/:propertyId/set-title to transfer title-ness, which rejects with 422 if the property is not text-compatible).

Request:

{
  "name": "Status (2025)",
  "position": "a0",
  "config": {
    "options": [
      { "id": "opt_01J9ZKQ8A1B3C5D7E9F1A3B5C7", "name": "Not Started", "color": "gray" },
      { "id": "opt_01J9ZKQ8B2C4D6E8F0A2B4C6D8", "name": "In Progress", "color": "blue" },
      { "id": "opt_01J9ZKQ8C3D5E7F9A1B3C5D7E9", "name": "Done", "color": "green" },
      { "id": "opt_01J9ZKQA1B3C5D7E9F1A3B5C7", "name": "Blocked", "color": "red" }
    ]
  }
}

200 OK

{
  "data": {
    "id": "prop_01J9ZKQ7G6B8C0D2E4F6A8B0C2",
    "name": "Status (2025)",
    "type": "select",
    "position": "a0",
    "config": { "options": [ "…" ] },
    "affectedRecords": 0
  }
}

Errors: 400 VALIDATION_ERROR (unknown option id not being added, invalid formula expression → details: [{field: "config.expression", issue: "formula_parse_error", position: 14}]), 403 FORBIDDEN, 404 NOT_FOUND, 409 CONFLICT (duplicate name).

Events emitted: database.updated (change: "property_updated").

DELETE /databases/:databaseId/properties/:propertyId

200 OK

{ "data": { "deletedProperty": "prop_01J9ZKQ9C9D1E3F5A7B9C1D3E5", "removedRelations": 0, "removedRollups": 1, "removedFormulas": 0, "affectedRecords": 128 } }

Events emitted: database.updated (change: "property_deleted").


3.4 Property Types — Config Shapes

All 24 PropertyType values from prisma/schema.prisma. (The PRODUCT-SPEC §9.1 heading says "22" but enumerates the same set the schema implements; the schema is authoritative.)

default is a full value of the type (see §7 Record values), null when none.

# type config shape (example) Stored value shape
1 title {} { "text": "Atlas migration", "marks": [] } (rich text; exactly one per database, isTitle: true)
2 text {} { "text": "One-line description", "marks": [] }
3 rich_text { "maxLines": null } { "text": "Longer **rich** body…", "marks": [ … ] }
4 number { "format": "number" | "percent" | "currency" | "scientific" | "decimal", "currency": "USD", "precision": 2 } { "value": 48250.5 }
5 select { "options": [ { "id", "name", "color" } ] } (colors: gray, brown, orange, yellow, green, blue, purple, pink, red, default) { "optionId": "opt_01J9ZKQ8B2C4D6E8F0A2B4C6D8" } (single)
6 multi_select same options array { "optionIds": [ "opt_…", "opt_…" ] }
7 status { "groups": [ { "name": "WIP", "optionIds": [ … ], "color": "blue" } ] } — options like select, grouped for board lanes { "optionId": "opt_…" } (single; renders in its group's color)
8 date { "includeTime": false, "timeZone": null } { "start": "2025-04-30T00:00:00.000Z", "end": null, "dateTimeTimezone": null }
9 date_range {} { "start": "2025-04-01T00:00:00.000Z", "end": "2025-04-30T00:00:00.000Z" }
10 person {} { "userIds": [ "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A" ] }
11 email {} { "value": "ada@example.com" }
12 phone { "defaultCountry": "GB" } { "value": "+44 20 7946 0958" }
13 url {} { "value": "https://factory.example.com", "title": "Factory" }
14 checkbox {} { "value": true }
15 files { "maxFiles": 10, "acceptedMimeTypes": [ "image/*", "application/pdf" ] } { "fileIds": [ "fil_01J9ZKP…", "fil_01J9ZKQ…" ] }
16 created_time {} read-only; { "value": "2025-01-02T09:21:00.000Z" } (server-set)
17 created_by {} read-only; { "userId": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A" }
18 last_edited_time {} read-only; { "value": "2025-01-02T16:02:00.000Z" }
19 last_edited_by {} read-only; { "userId": "usr_01J9ZKQ2V4W6X8Y0Z2A4B6C8D0" }
20 relation { "targetDatabaseId": "db_01J9ZKR1D2E4F6A8B0C2D4E6", "cardinality": "one_to_one" | "one_to_many" | "many_to_many", "bidirectional": true, "reverseName": "Related Projects" } { "recordIds": [ "rpt_01J9ZKR2E3F5A7B9C1D3E5" ] }
21 rollup { "relationPropertyId": "prop_…", "targetPropertyId": "prop_…", "aggregate": "COUNT" | "SUM" | "AVERAGE" | "MIN" | "MAX" | "PERCENT" | "UNIQUE", "filter": <filter tree, see §5.3> } computed, read-only; e.g. { "value": 4 }, { "values": [ … ] } for UNIQUE
22 formula { "expression": "if(prop(\"Status\") == \"Done\", 100, 0)" } — parsed to an AST by the safe evaluator (never JS eval); invalid → 400 VALIDATION_ERROR computed, read-only; { "value": 100, "type": "number" }
23 button { "actions": [ { "type": "set_property", "config": { "propertyId": "prop_…", "value": { "optionId": "opt_…" } } }, { "type": "send_webhook", "config": { "webhookId": "webh_…" } }, { "type": "trigger_automation", "config": { "automationId": "auto_…" } } ] } not stored per record; null
24 unique_id { "prefix": "PRJ-" } server-generated, read-only; { "value": "PRJ-000007" } (zero-padded, monotonic per database)

Read-only types (created_time, created_by, last_edited_time, last_edited_by, rollup, formula, unique_id, button) reject writes with 422 UNPROCESSABLE_ENTITY (details: [{issue: "read_only_property", property: "…"}]).

Option ids are optional on write, guaranteed on read. Creating or updating a select / multi_select / status property may omit id on one or more config.options entries (the AI generation path emits bare { name, color }). The server then assigns each id-less option a deterministic id derived from its name (slug-style, e.g. To Do → opt-to-do; NFKD-normalized, lowercased, non-alphanumerics → -) and persists the normalized config. A partial update that re-lists an existing option without its id keeps that option's id (custom ids included) so stored optionId values stay resolvable. Record values written with the option name as the optionId are canonicalized to the id at write time; values already stored as names (legacy rows) are resolved to the id at read time (record DTOs, board grouping, filter evaluation, CSV export). Clients may therefore always match a stored optionId against option.id; the server never returns a value whose reference is an unknown option name.

The generator (optionIdForName) and the config backfill (normalizeSelectOptions) live in @factory/shared as the single source of truth — the web app (which cannot depend on @factory/ai) and the API share the exact same function, so a stored name and its canonical id always resolve identically on both sides. Every read boundary normalizes: GET /databases/:id, GET /databases/:id/properties, and the record-open endpoint — the latter two fill the same 60 s column-metadata cache, so both loaders store the same normalized entry (whichever fills first wins the TTL; an id-less entry from one path would have blanked select/status cells in the other's view). The web additionally re-normalizes at its client read boundary (flattenDatabase), so even a stale or legacy option list in an open tab degrades to the name-fallback render instead of a blank cell.


4. Relations & Rollups (property-level)

Relations and rollups are created as properties (types 20 and 21 above); no separate endpoints. The server materializes:

Example — create a relation property:

curl -s -X POST https://factory.example.com/api/v1/databases/db_01J9ZKQ7E4F6A8B0C2D4E6F8A0/properties \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
  -d '{
    "name": "Tasks",
    "type": "relation",
    "config": {
      "targetDatabaseId": "db_01J9ZKR1D2E4F6A8B0C2D4E6",
      "cardinality": "one_to_many",
      "bidirectional": true,
      "reverseName": "Project"
    }
  }'

5. Views

A view is a saved lens on the records: its own filters, sort, group, visible properties, and presentation config. View config is a JSONB free-form object (per-type fields below); filter/sort/group are top-level fields so the query engine can read them uniformly.

5.1 CRUD

Endpoint Auth Notes
POST /databases/:databaseId/views WRITE Body: { "name", "type" (table|list|board|gallery|calendar|timeline|chart|dashboard|form), "config"?, "filter"?, "sort"?, "group"?, "pageSize"?, "position"? } → 201 view object.
GET /databases/:databaseId/views READ { data: [views] } by position.
PATCH /databases/:databaseId/views/:viewId WRITE Merge-patch any of name, config, filter, sort, group, pageSize, position. type immutable (422).
DELETE /databases/:databaseId/views/:viewId WRITE 204. The last remaining view cannot be deleted (422, details: [{issue: "last_view"}]).

View object:

{
  "id": "view_01J9ZKQ8F5A7B9C1D3E5F7A9B1",
  "databaseId": "db_01J9ZKQ7E4F6A8B0C2D4E6F8A0",
  "name": "Board",
  "type": "board",
  "position": "a1",
  "config": {
    "visibleProperties": ["prop_01J9ZKQ7F5A7B9C1D3E5F7A9B1", "prop_01J9ZKQ7G6B8C0D2E4F6A8B0C2"],
    "cardProperties": ["prop_01J9ZKQ7H7C9D1E3F5A7B9C1D3", "prop_01J9ZKQ7I8D0E2F4A6B8C0D2E4"],
    "columnWidth": null,
    "cardLayout": "cover",
    "color": null,
    "density": "comfortable"
  },
  "filter": {
    "op": "and",
    "conditions": [
      { "op": "or", "conditions": [
        { "propertyId": "prop_01J9ZKQ7G6B8C0D2E4F6A8B0C2", "op": "equals", "value": { "optionId": "opt_01J9ZKQ8B2C4D6E8F0A2B4C6D8" } },
        { "propertyId": "prop_01J9ZKQ7G6B8C0D2E4F6A8B0C2", "op": "equals", "value": { "optionId": "opt_01J9ZKQ8E5F7A9B1C3D5E7F9A1" } }
      ] },
      { "propertyId": "prop_01J9ZKQ7I8D0E2F4A6B8C0D2E4", "op": "contains_any", "value": { "userIds": ["usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A"] } }
    ]
  },
  "sort": [ { "propertyId": "prop_01J9ZKQ7J9E1F3A5B7C9D1E3F5", "direction": "asc" } ],
  "group": [ { "propertyId": "prop_01J9ZKQ7G6B8C0D2E4F6A8B0C2", "direction": "asc" } ],
  "pageSize": null,
  "recordCount": 37,
  "createdAt": "2025-01-02T09:21:00.000Z",
  "updatedAt": "2025-01-02T11:30:00.000Z"
}

Events emitted: database.updated (change: "view_added" | "view_updated" | "view_deleted").


5.2 Per-View config Fields

All view types share: visibleProperties (property ids, in order), color, density (compact | comfortable | spacious).

type Extra config fields
table columnWidths: { "<propertyId>": 180 }, headerRow: true, zebra: true
list showIcons: true, subtitlePropertyId: "prop_…"
board cardProperties: [ids shown on card], cardLayout: "cover" | "title" | "compact", columnWidthPx: 280
gallery coverPropertyId: "prop_…", cardsPerRow: 4, cardRatio: "16:9"
calendar startDatePropertyId: "prop_…", endDatePropertyId: "prop_…" (either may be null = end-of-day of start), weekStartsOn: 1, monthDensity: "comfortable"
timeline startDatePropertyId, endDatePropertyId, scale: "day" | "week" | "month" | "quarter", lanePropertyId (optional swimlane grouping)
chart chartId: "cht_…" (links a charts row: chartType: bar|line|area|pie|donut|number, axes, aggregation, color, legend — see Product Spec §12)
dashboard dashboardId: "dash_…" (links a dashboards row with layout widgets)
form formId: "frm_…" (links a forms row; public URL <host>/forms/<slug>), intro, successMessage, branding

Validation: calendar/timeline require the date properties to exist and be of type date/date_range → else 422 UNPROCESSABLE_ENTITY (details: [{issue: "view_config_invalid", field: "startDatePropertyId"}]).


5.3 Filter Trees (JSON Schema)

Filters are nested boolean trees of and / or / not over property conditions — supporting e.g. (Status = In Progress OR Status = Review) AND Owner = Current User.

filter := { "op": "and" | "or" | "not", "conditions": filter[] }
       |  { "propertyId": string, "op": <condition op>, "value": <per-type> }

Condition ops by property kind:

Kind Ops value shape
All (general) is_empty, is_not_empty, is, is_not is/is_not take a per-type value
text / rich_text / title / email / url contains, does_not_contain, starts_with, ends_with { "text": "atlas" }
number equals, not_equals, greater_than, less_than, greater_or_equal, less_or_equal { "value": 1000 }
select / status equals, not_equals, contains_any, contains_all { "optionId": "opt_…" } / { "optionIds": [ … ] }
multi_select contains_any, contains_all, is_empty, is_not_empty { "optionIds": [ … ] }
date / date_range equals, before, after, between, on_or_before, on_or_after, is_this_week, is_this_month, within_next_days, is_past, is_future { "value": "2025-04-30T00:00:00.000Z" }, between: { "start": "…", "end": "…" }, within_next_days: { "days": 7 }
checkbox is, is_not { "value": true }
person contains_any, contains_all, is_current_user { "userIds": [ … ] } / {}
relation contains_any, contains_all, is_empty, is_not_empty { "recordIds": [ … ] }
files is_empty, is_not_empty —
rollup / formula (numeric) same as number { "value": … }

Full example (the one from the product spec):

{
  "op": "and",
  "conditions": [
    {
      "op": "or",
      "conditions": [
        { "propertyId": "prop_status", "op": "equals", "value": { "optionId": "opt_in_progress" } },
        { "propertyId": "prop_status", "op": "equals", "value": { "optionId": "opt_review" } }
      ]
    },
    { "propertyId": "prop_owner", "op": "is_current_user", "value": {} }
  ]
}

Rules: not takes exactly one child; and/or take ≥ 1; leaves require propertyId, op, and a value compatible with the op (422 UNPROCESSABLE_ENTITY with details: [{issue: "invalid_filter", path: "conditions[0].conditions[1]"}] when not).


5.4 Sort & Group Shapes

"sort": [ { "propertyId": "prop_…", "direction": "asc" | "desc" } ]
"group": [ { "propertyId": "prop_…", "direction": "asc" | "desc" } ]

6. Query DSL

POST /databases/:databaseId/query

The full query endpoint — the same engine that powers view rendering, usable ad-hoc by API clients. One request, fully server-side evaluated.

Request:

{
  "select": ["prop_01J9ZKQ7F5A7B9C1D3E5F7A9B1", "prop_01J9ZKQ7G6B8C0D2E4F6A8B0C2", "prop_01J9ZKQ7K0F2A4B6C8D0E2F4A6"],
  "filter": {
    "op": "and",
    "conditions": [
      { "propertyId": "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2", "op": "not_equals", "value": { "optionId": "opt_01J9ZKQ8C3D5E7F9A1B3C5D7E9" } },
      { "propertyId": "prop_01J9ZKQ7L1G3B5C7D9E1F3A5B7", "op": "after", "value": { "value": "2025-01-01T00:00:00.000Z" } }
    ]
  },
  "sort": [ { "propertyId": "prop_01J9ZKQ7L1G3B5C7D9E1F3A5B7", "direction": "desc" } ],
  "group": [ { "propertyId": "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2", "direction": "asc" } ],
  "aggregate": [
    { "propertyId": "prop_01J9ZKQ7M2H4C6D8E0F2A4B6C8", "fn": "SUM" }
  ],
  "includeArchived": false,
  "cursor": null,
  "pageSize": 50
}
Field Notes
select Property ids to return per record. Omit → all visible properties. Rollup/formula properties may be selected (computed on the fly, cached for 60 s).
filter Any valid filter tree (§5.3). Omit → no filter.
sort As in views. Omit → record position order.
group Group records; response is nested per group (see below).
aggregate 0–8 entries: { "propertyId", "fn": "COUNT"|"SUM"|"AVERAGE"|"MIN"|"MAX"|"UNIQUE" }. COUNT may use propertyId: "*" for row count. Aggregates respect the filter but ignore group (global over the filtered set) unless perGroup: true.
includeArchived Default false — archived records are hidden.
cursor, pageSize pageSize 1–200, default 50.

200 OK — grouped shape when group is set:

{
  "data": {
    "groups": [
      {
        "key": { "propertyId": "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2", "value": { "optionId": "opt_01J9ZKQ8B2C4D6E8F0A2B4C6D8" }, "label": "In Progress" },
        "count": 12,
        "records": [
          {
            "id": "rpt_01J9ZKR3F4A6B8C0D2E4F6A8B0",
            "pageId": "pg_01J9ZKR4A5B7C9D1E3F5A7B9C1",
            "icon": "🛰️",
            "position": "a0",
            "createdTime": "2025-01-02T09:22:00.000Z",
            "lastEditedTime": "2025-01-02T16:00:00.000Z",
            "properties": {
              "prop_01J9ZKQ7F5A7B9C1D3E5F7A9B1": { "text": "Atlas migration", "marks": [] },
              "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2": { "optionId": "opt_01J9ZKQ8B2C4D6E8F0A2B4C6D8" },
              "prop_01J9ZKQ7K0F2A4B6C8D0E2F4A6": { "value": 48250.5 }
            }
          }
        ]
      },
      {
        "key": { "propertyId": "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2", "value": { "optionId": "opt_01J9ZKQ8E5F7A9B1C3D5E7F9A1" }, "label": "Medium" },
        "count": 3,
        "records": [ "…" ]
      }
    ],
    "aggregates": { "prop_01J9ZKQ7M2H4C6D8E0F2A4B6C8": 182340.75 },
    "totalCount": 15,
    "nextCursor": "ZGVlcDoxOjpycHRfMDFKSlo…"
  }
}

Without group, data is the flat envelope from §6.1 plus an optional aggregates object.

Errors: 400 VALIDATION_ERROR (bad aggregate fn), 401 UNAUTHORIZED, 403 FORBIDDEN, 404 NOT_FOUND, 422 UNPROCESSABLE_ENTITY (filter tree, sort/group property type mismatch).

Performance: on 100k+ record databases, queries with indexed property filters run < 300 ms p95; unfiltered full scans return pages of 200 at a time and should always pass a pageSize.

Events emitted: none (read-only).


6.1 GET /databases/:databaseId/records — View-Rendered Listing

List records with explicit query params (the alternative to the DSL; handy for curl and simple clients). All params are optional:

Param Type Notes
viewId string Load the view's filter/sort/group/config (then override with explicit params below).
filter JSON string URL-encoded filter tree overriding the view filter.
sort JSON string URL-encoded [{propertyId, direction}] overriding view sort.
group JSON string URL-encoded group spec.
includeArchived boolean Default false.
subItemOf string Only sub-records of this record (see §7.5).
cursor string
pageSize int 1–200, default 50.

200 OK — flat or grouped exactly like the DSL response.


7. Records

A record (rpt_…) is a row and a page: pageId is 1:1 with pages.id. You can read/edit the record's blocks via the normal page/block endpoints (GET /pages/:pageId?include=blocks, POST /blocks), and its properties via the record endpoints below.

7.1 Record Object

{
  "id": "rpt_01J9ZKR3F4A6B8C0D2E4F6A8B0",
  "workspaceId": "wks_01J9ZKP5C9H7J9K1L3M5N7P9Q1",
  "databaseId": "db_01J9ZKQ7E4F6A8B0C2D4E6F8A0",
  "pageId": "pg_01J9ZKR4A5B7C9D1E3F5A7B9C1",
  "icon": "🛰️",
  "coverUrl": null,
  "archivedAt": null,
  "isSubItem": false,
  "position": "a0",
  "version": 4,
  "createdBy": "usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A",
  "createdTime": "2025-01-02T09:22:00.000Z",
  "lastEditedBy": "usr_01J9ZKQ2V4W6X8Y0Z2A4B6C8D0",
  "lastEditedTime": "2025-01-02T16:00:00.000Z",
  "properties": {
    "prop_01J9ZKQ7F5A7B9C1D3E5F7A9B1": { "text": "Atlas migration", "marks": [] },
    "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2": { "optionId": "opt_01J9ZKQ8B2C4D6E8F0A2B4C6D8" },
    "prop_01J9ZKQ7H7C9D1E3F5A7B9C1D3": { "userIds": ["usr_01J9ZKQ2V4W6X8Y0Z2A4B6C8D0"] },
    "prop_01J9ZKQ7I8D0E2F4A6B8C0D2E4": { "value": 48250.5 },
    "prop_01J9ZKQ7J9E1F3A5B7C9D1E3F5": { "start": "2025-03-01T00:00:00.000Z", "end": "2025-04-30T00:00:00.000Z" },
    "prop_01J9ZKQ7K0F2A4B6C8D0E2F4A6": { "optionIds": ["opt_01J9ZKQ9A7B9C1D3E5F7A9B1C3"] }
  }
}

7.2 Create a Record

POST /databases/:databaseId/records

Request:

{
  "properties": {
    "prop_01J9ZKQ7F5A7B9C1D3E5F7A9B1": { "text": "Borealis rewrite", "marks": [] },
    "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2": { "optionId": "opt_01J9ZKQ8A1B3C5D7E9F1A3B5C7" },
    "prop_01J9ZKQ7H7C9D1E3F5A7B9C1D3": { "userIds": ["usr_01J9ZKQX4V2M8N3P5R7T9W1Y3A"] },
    "prop_01J9ZKQ7J9E1F3A5B7C9D1E3F5": { "start": "2025-02-10T00:00:00.000Z", "end": "2025-05-20T00:00:00.000Z" },
    "prop_01J9ZKQ7M2H4C6D8E0F2A4B6C8": { "recordIds": ["rpt_01J9ZKR3F4A6B8C0D2E4F6A8B0"] }
  },
  "icon": "🌌",
  "position": "a0",
  "parentRecordId": null
}
Field Notes
properties Map of propertyId → typed value (shapes in §3.4). The title property is required (400 if missing/empty for a required title). Unknown property ids → 400. Read-only types → 422.
icon, coverUrl Page-level decoration for the record page.
position Insertion point in table/board order; default append.
parentRecordId Create as a sub-record (see §7.5).

201 Created — full record object (as §7.1), with the generated id, pageId, createdTime, and unique_id property values filled in.

Errors: 400 VALIDATION_ERROR (per-property details), 401 UNAUTHORIZED, 403 FORBIDDEN, 404 NOT_FOUND, 422 UNPROCESSABLE_ENTITY (read-only property, relation to a different workspace's record), 429 RATE_LIMITED.

Events emitted: record.created (includes databaseId, pageId, and a snapshot of the property values for webhooks/indexing).


7.3 Update a Record

PATCH /records/:recordId

Request — partial property map (only listed properties change):

{
  "properties": {
    "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2": { "optionId": "opt_01J9ZKQ8C3D5E7F9A1B3C5D7E9" },
    "prop_01J9ZKQ7I8D0E2F4A6B8C0D2E4": { "value": 61200.0 },
    "prop_01J9ZKQ7M2H4C6D8E0F2A4B6C8": { "recordIds": ["rpt_01J9ZKR3F4A6B8C0D2E4F6A8B0", "rpt_01J9ZKR5B6C8D0E2F4A6B8C0D2"] }
  },
  "position": "a0V",
  "icon": "🌌",
  "version": 4
}

200 OK — full record object with the bumped version, lastEditedTime, lastEditedBy.

Errors: 400 VALIDATION_ERROR, 401 UNAUTHORIZED, 403 FORBIDDEN, 404 NOT_FOUND, 409 CONFLICT (stale version), 422 UNPROCESSABLE_ENTITY (read-only, relation integrity), 429 RATE_LIMITED.

Events emitted: record.updated (payload includes changedPropertyIds and old/new values for changed properties — this is what automation PROPERTY_CHANGED triggers consume).


7.4 Read / Archive / Delete a Record

GET /records/:recordId

POST /records/:recordId/archive

Soft-archive: archivedAt set; hidden from views/queries by default (includeArchived: true to see). The record page is not trashed.

POST /records/:recordId/unarchive

DELETE /records/:recordId

Hard-delete the record and its page (blocks, comments cascade).

Request: { "confirm": "rpt_01J9ZKR3F4A6B8C0D2E4F6A8B0" }

202 Accepted

{ "data": { "id": "rpt_01J9ZKR3F4A6B8C0D2E4F6A8B0", "status": "deleting", "pageId": "pg_01J9ZKR4A5B7C9D1E3F5A7B9C1" } }

Events: record.deleted (audit).


7.5 Sub-Records

Records can be nested (a "sub-item" of another record in the same database) — rendered as an indented row in table views. Create with parentRecordId (POST) or update with parentRecordId (PATCH, may be null to detach). Cycles are rejected (422 cycle_detected); max depth 10. List children with GET /databases/:id/records?subItemOf=<recordId>; the record object exposes parentRecordId and subItemCount.


8. curl Examples

# Create a database embedded in a page
curl -s -X POST https://factory.example.com/api/v1/databases \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
  -d '{
    "title": "Projects",
    "pageId": "pg_01J9ZKQ4F8H5I7J9L1M3N5P7Q9",
    "properties": [
      { "name": "Name", "type": "title" },
      { "name": "Status", "type": "select", "config": { "options": [
          { "id": "opt_01J9ZKQ8A1B3C5D7E9F1A3B5C7", "name": "Not Started", "color": "gray" },
          { "id": "opt_01J9ZKQ8B2C4D6E8F0A2B4C6D8", "name": "In Progress", "color": "blue" },
          { "id": "opt_01J9ZKQ8C3D5E7F9A1B3C5D7E9", "name": "Done", "color": "green" } ] } },
      { "name": "Owner", "type": "person" }
    ],
    "views": [ { "name": "Table view", "type": "table" }, { "name": "Board", "type": "board" } ]
  }'

# Create a record
curl -s -X POST https://factory.example.com/api/v1/databases/db_01J9ZKQ7E4F6A8B0C2D4E6F8A0/records \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
  -d '{ "properties": {
        "prop_01J9ZKQ7F5A7B9C1D3E5F7A9B1": { "text": "Atlas migration", "marks": [] },
        "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2": { "optionId": "opt_01J9ZKQ8B2C4D6E8F0A2B4C6D8" } } }'

# Update one property (optimistic concurrency)
curl -s -X PATCH https://factory.example.com/api/v1/records/rpt_01J9ZKR3F4A6B8C0D2E4F6A8B0 \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' -H 'If-Match: "4"' \
  -d '{ "properties": { "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2": { "optionId": "opt_01J9ZKQ8C3D5E7F9A1B3C5D7E9" } } }'

# Full DSL query: open projects due in the next 30 days, grouped by status
curl -s -X POST https://factory.example.com/api/v1/databases/db_01J9ZKQ7E4F6A8B0C2D4E6F8A0/query \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
  -d '{
    "select": ["prop_01J9ZKQ7F5A7B9C1D3E5F7A9B1", "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2", "prop_01J9ZKQ7J9E1F3A5B7C9D1E3F5"],
    "filter": { "op": "and", "conditions": [
      { "propertyId": "prop_01J9ZKQ7J9E1F3A5B7C9D1E3F5", "op": "within_next_days", "value": { "days": 30 } } ] },
    "group": [ { "propertyId": "prop_01J9ZKQ7G6B8O0D2E4F6A8B0C2", "direction": "asc" } ],
    "aggregate": [ { "propertyId": "*", "fn": "COUNT" } ],
    "pageSize": 100
  }'

# View-rendered listing via params (board view)
curl -s "https://factory.example.com/api/v1/databases/db_01J9ZKQ7E4F6A8B0C2D4E6F8A0/records?viewId=view_01J9ZKQ8G6B8C0D2E4F6A8B0C2&pageSize=50" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

9. Events Summary

Operation Domain event(s)
Create database database.created (+ page.updated when embedded)
Update / archive database database.updated
Property add / update / delete database.updated (change: property_added / property_updated / property_deleted)
View add / update / delete database.updated (change: view_added / view_updated / view_deleted)
Create record record.created
Update record record.updated (with changedPropertyIds + old/new values)
Archive / unarchive record record.archived / record.unarchived
Delete record record.deleted (audit)
Query — (read-only)