Every request takes the same path: the browser talks only to the BFF, the BFF adds the bearer token and proxies to the API, and the API authorizes, executes, queues its audit event in the same transaction and answers. The client has no direct access to the database or any service.
The API
A versioned REST API under /api/v1, with a machine-readable OpenAPI contract published by the service itself. The web application is its first and only client: every rule on a screen is enforced here, so a script cannot do what a screen refuses. Every operation, who may call it and where is on the generated API reference.
| Endpoint | Purpose |
|---|---|
GET /api/v1/openapi.json | The OpenAPI document; the client's typed API client is generated from it |
GET /api/v1/docs | Swagger UI for browsing, which loads its scripts from a public CDN and relaxes the page's content-security policy |
GET /healthz, GET /readyz | Liveness and readiness probes, ahead of authentication |
GET /api/v1/_internal/* | The operator's diagnostics: authenticated and system.monitor |
The contract and the docs page need no sign-in so that tooling can fetch them; in production they should sit behind the same network boundary as the internal diagnostics.
Shape
/api/v1/auth/* sign-in, codes, organization selection, entering a workspace, refresh, sessions
/api/v1/auth/invitation/* what an invitation link says, and a fresh link
/api/v1/platform/provision/* the control plane's handshake (service principal)
/api/v1/platform/organization/* the setup wizard and the organization's overview
/api/v1/platform/{users,rbac,access-exceptions,access-report,audit,org-audit,library,mail}/*
/api/v1/notifications/* in-app notifications
/api/v1/reports/* cross-study rollups
/api/v1/studies the study collection
/api/v1/studies/:studyId/* everything inside a studyEvery study-scoped route mounts behind authentication and study resolution, then declares its own access: the license for the feature where it is switched, membership, permission, the workspace kind it needs, and the study-state intent of a write. See Who can and cannot.
Authentication
Bearer tokens (signed JWTs). The web application obtains one through the passwordless code flow and keeps it server-side; a direct consumer performs the same flow (Signing in). Tokens are short-lived, with refresh rotation and reuse detection (a refresh token presented twice ends the session). The token binds the organization the database isolates on, and the session's workspace; there are no API keys, no OAuth clients and no single sign-on. A signature needs a further step-up code for the single record it signs.
Conventions
- Request bodies, query strings and path parameters are schema-validated at the edge (zod).
- Errors return one envelope with a stable code, a message, the HTTP status and the request id; the codes are on Error messages.
- A study the caller is not a member of answers 404, never 403: existence is not confirmed.
- Rate limiting applies globally, and more strictly to authentication; both count the viewer's address as the web application vouches for it, not the proxy's.
- Large lists are paginated; the audit ledgers and long lists use keyset pagination so paging does not drift while events arrive.
- Every mutating request is audited as a request event, in addition to the domain events a change writes, and every denial is recorded as an access decision; successful reads are not recorded (Not built yet).
What the API integrates with today
| Direction | What | How |
|---|---|---|
| In | The control plane provisions an organization and its first administrator, reads an organization's invitations and counts, and suspends or restores it | Service-principal endpoints under /platform; every call is on the control plane's own ledger |
| In | The mail service reports delivered, bounced or failed for a message | POST /api/v1/platform/mail/events, no session; an HMAC of the body authenticates it |
| Out | The control plane is asked which features an organization may use | A narrow key (CONTROL_PLANE_ENTITLEMENTS_KEY) that opens one route; cached 60 seconds; if it cannot be reached the feature is allowed and a warning is logged |
| Out | SMTP through nodemailer | |
| Out | Storage | S3: documents (presigned links), exports and audit anchors |
| Out | AI | Google Vertex AI, for the optional form generator and role assistant |
| Data in | Participants and sites by CSV | The Import action on a site's page and the Sites page (Screen a participant, Open a site) |
| Data out | Excel, CSV and CDISC ODM-XML, and design exports | Export jobs, produced by the worker (Export data) |
What cannot be integrated today
There are no purpose-built integrations. Specifically absent:
- Randomization, IRT or IWRS: no allocation API to call or be called by. A participant randomized elsewhere is recorded with the external identifiers (Enroll a participant).
- Safety systems: no E2B generation, no gateway, no comparison with a safety database; the reconciliation is a person's entry.
- Central labs: no lab loading pipeline, no LOINC handling, no reference-range service.
- EHR, imaging, wearables, ePRO or eCOA, CTMS: nothing.
- Outbound webhooks: the system does not call out on events; the notifier names only e-mail and in-app delivery.
- Single sign-on (SAML, OIDC) and API keys: the
ssoandapi.accessswitches exist in the platform console and gate nothing (Feature switches). - SAS,
define.xml, PDF and signed exports: refused, not produced.
Anything you build uses the REST API with a person's session, and polls.
Related
API reference · Architecture · AI · Not built yet · Platform console