HTTP API reference
This document enumerates every HTTP endpoint exposed by the Find My Data server (release 26.7.15.0), for integrators building against the API and reviewers auditing its authorization surface. Every path, capability string, and field name below is taken directly from the route modules mounted inpackages/server/src/app.ts; the authorization and error-envelope behavior is defined in packages/server/src/kernel/http.ts and packages/server/src/kernel/errors.ts. Role-to-capability grants are documented in the permissions manifest; the security rationale is in the threat model.
Conventions
- All endpoints live under
/api/. Requests and responses are JSON unless noted (the Graph webhook validation handshake respondstext/plain). - Request bodies are validated with Zod schemas; a failed parse returns
400 validation_failedwith per-field issues (see Error envelope). - An optional
x-fmd-correlationrequest header sets the correlation id for the request; otherwise one is generated. Responses carry anx-fmd-auth-modeheader (devorentra) — except thecsrf_token_invalidrejection, which is returned before the header is set. - List endpoints are bounded (e.g. assets 200 rows, findings 200, audit 200, scans 50, analyst history 25); there is no cursor pagination in this release.
Capability column legend
Authentication model
Session cookie
Authentication is a signed session cookie namedfmd_session (SESSION_COOKIE in kernel/http.ts): httpOnly, SameSite=Lax, Secure when FMD_ENV=production, path /. The cookie value is an HMAC-signed session id; the server resolves it to a session row and loads the authorization context (tenant, principal, roles, owned domains) per request. Client-supplied identity fields are never trusted.
Two auth modes exist (ADR-0004):
-
Dev mode (
FMD_AUTH_MODE=dev, development only). Login flow:GET /api/auth/dev/personas— list seeded principals with roles.POST /api/auth/dev/loginwith{ "principalId": "..." }— sets the cookie and returns{ ok, csrfToken, authMode }.
FMD_AUTH_MODE=devwhenFMD_ENV=production(kernel/config.ts), and the request middleware rejects any surviving dev-mode session at request time (kernel/http.ts). -
Entra mode (
FMD_AUTH_MODE=entra) — not built.GET /api/auth/entra/loginreturns an explicit501 not_configuredpointing at the integration seam; it is never a silent fallback.
CSRF
Every mutating request (POST, PUT, PATCH, DELETE) made with a session must send the session’s CSRF token in the x-fmd-csrf header. The token is returned by POST /api/auth/dev/login and by GET /api/auth/me. A missing or wrong token returns 403 {"error":"csrf_token_invalid"}. Requests without a resolved session skip the CSRF check — this is what allows the unauthenticated Graph webhooks and the dev login itself to POST.
401 / 403 / 404 semantics
Authorization is capability-based, not role-based: handlers call
requireCapability(auth, "..."), and capabilities are granted per role at either tenant scope or domain scope (resolved against active ownership assignments). Domain-scoped list endpoints additionally apply domainScopeFilter so results are constrained to the caller’s owned domains. See the permissions manifest for the full role/capability matrix.
Error envelope
The central error handler (kernel/http.ts) maps typed errors (kernel/errors.ts) to a uniform envelope:
- Zod validation failures:
{ "error": "validation_failed", "issues": [{ "path": "field.path", "message": "..." }] }(400). - CSRF failures:
{ "error": "csrf_token_invalid" }(403, nomessage). - Unhandled errors:
{ "error": "internal_error", "correlationId": "..." }(500) — no stack or details leak.
Endpoint reference
83 endpoints across 14 route modules, in mount order frompackages/server/src/app.ts.
Health (routes/health.ts)
Liveness and honest readiness. No authentication.
GET /api/ready returns { status, dbOk, env, integrations } where each integration (connector, aiProvider, entraSignIn, purviewLabelRead, purviewLabelWrite, changeNotifications, teamsConnector, exchangeConnector) reports one of:
mock— the deterministic fixture path is in use,live— feature enabled and its secret reference actually resolves to a value,blocked— feature enabled but credentials do not resolve (never reported aslive),disabled— feature turned off.
blocked/disabled split: purviewLabelRead and purviewLabelWrite report blocked (never disabled) whenever they are not fully live — including when their feature flag is simply off — and entraSignIn reports blocked whenever FMD_AUTH_MODE=entra because the OIDC flow is not built (disabled otherwise). changeNotifications additionally requires a public webhook URL (FMD_WEBHOOK_PUBLIC_URL) to report live.
Auth (identity/auth-routes.ts)
Governance (governance/routes.ts)
Onboarding, taxonomy lifecycle (draft → human review → approval), and ownership assignment. AI-generated taxonomy objects are always drafts pending human review.
Scan (scan/routes.ts)
Connector registration and durable scan campaigns (ADR-0007, ADR-0008). The mock M365 connector is the default/CI path; the live Graph connector is implemented and was validated read-only against the live CDX tenant (ADR-0013, CDX runbook).
Scope visibility: tenant-scoped readers (admin/auditor) see all scopes; a domain-scoped owner sees scopes governed by their domains or holding at least one asset classified into them.
Models (routes/models.ts)
Governed dataset curation and model-release lifecycle with separation of duties (approver ≠ proposer, enforced in models/pipeline.ts). This loop ran end-to-end on live CDX data.
Review (review/routes.ts)
The “Is this yours?” owner review queue. Decisions create linked OwnerConfirmed assertions — the original inference is never destroyed — and propagate to cluster/container neighbors without auto-confirming.
Analyst (analyst/routes.ts)
Typed, scoped query answering over the search projection (ADR-0009). Plans — whether deterministic or AI-proposed — are validated identically, and tenant/domain predicates are injected server-side; a plan cannot widen its scope.
Actions (actions/routes.ts)
Approval-gated remediation state machine (ADR-0010): draft (with preview + justification) → submit → approve → execute → verify from source. internal_task runs fully in-product. purview_label_set / purview_label_remove are built and offline/mock-validated; the live Graph write path is feature-gated (FMD_FEATURE_PURVIEW_LABEL_WRITE + a separate remediation identity) and refuses honestly when unconfigured: the draft is recorded with status: "blocked" and a blockedReason, and submitting a blocked action returns 409 conflict (a live connector whose base Graph credentials do not resolve fails earlier with 422 blocked_capability). Live label writes have not been exercised against a real tenant.
Read models (routes/read-models.ts)
Owner-facing read surface. Every query is tenant-scoped; domain-scoped capabilities filter to owned domains. Evidence levels are tiered (metadata < summary < excerpt); excerpt reads require asset.evidence.excerpt.read and are audited.
Admin (admin/routes.ts)
Setup progress, runtime + tenant settings, and a connectivity probe. Secrets are never returned — only booleans derived from configuration references.
Notifications (notifications/routes.ts)
Graph change-notification lifecycle: built end-to-end and validated via the dev simulator, which drove a real read-only delta reconcile against the live CDX tenant. Live Graph subscription delivery additionally requires FMD_FEATURE_CHANGE_NOTIFICATIONS=true and a public HTTPS receiver URL (FMD_WEBHOOK_PUBLIC_URL); /api/ready reports the mode honestly. A notification is treated as a hint that triggers delta reconciliation — notification content is never trusted.
Webhook trust model (both unauthenticated endpoints):
- Validation handshake — a request with a
?validationToken=query parameter is echoed back astext/plain 200(Graph’s subscription-creation check). - clientState HMAC — each subscription’s
clientStateis derived asHMAC-SHA256(sessionSecret, "fmd-sub:<subscriptionId>")(deriveClientState) and compared in constant time (constantTimeEquals). On the notifications receiver,ingestNotificationrejects any item whoseclientStatedoes not verify before any side effect, and deliberately does not persist rejected rows (an unauthenticated caller who learns a subscription id must not be able to amplify writes); a metric records the rejection. On the lifecycle receiver, the same constant-time check runs beforehandleLifecycleEvent— an absent or wrongclientStateis skipped, so an unauthenticated caller cannot force a reconcile or tamper with a subscription. - Verified notifications are deduplicated within a time window and enqueue a delta reconcile; the payload itself is never treated as content.
Teams (teams/routes.ts)
Teams connector surface — built and mock-validated; the live Graph adapter is feature-gated behind FMD_FEATURE_TEAMS_CONNECTOR (requires the protected ChannelMessage.Read.All permission) and has not been run live. Channel file attachments canonicalize to their backing SharePoint driveItem, so no duplicate asset identities are created. Read views require tenant-scoped operations.read because Teams messages are not yet mapped to business-domain ownership — deny-by-default until they are.
Exchange (exchange/routes.ts)
Exchange connector surface — built and mock-validated; live is feature-gated behind FMD_FEATURE_EXCHANGE_CONNECTOR (Mail.Read scoped to selected mailboxes via FMD_EXCHANGE_MAILBOXES) and has not been run live. An attachment is a copy: a content match to a SharePoint asset is recorded as similarity, not identity. Read views require tenant-scoped operations.read for the same deny-by-default reason as Teams.
Control plane (controlplane/routes.ts)
Signed licensing entitlement (verified locally, offline grace window — no network calls from these endpoints) and strictly allowlisted fleet-health telemetry with a transparency preview and customer opt-out. This module also defines PRODUCT_VERSION = "26.7.15.0".
Related documents
- Permissions manifest — roles, capability grants, and Microsoft Graph permission requirements
- Threat model — the security invariants these endpoints enforce
- Architecture — where the route modules sit in the system
- CDX test runbook — record of what has been validated against the live CDX tenant
- ADR-0003 — persistence model behind these APIs
- ADR-0004 — dev/Entra authentication design