Skip to content
ImplementedFor developers, integrators, QA, auditors

API reference

Last updated Oct 21, 2026

What the data plane's HTTP API offers, who may call each operation and where. The web application uses nothing else: every rule on the screens is enforced here, so a script cannot do what a screen refuses.

How to read it

  • Base path /api/v1. Paths show {studyId} and similar as parameters.
  • Who may call. A permission means the caller must hold any one of those permissions on the study (and site) the request names; see Roles and permissions. No administrator override means the Organization Admin does not pass either: clinical and signing actions are checked strictly. Any signed-in user means a valid session and nothing more. No sign-in needed is limited to asking for a code, refreshing a session and the health probes.
  • Workspace. none: the operation touches nothing a workspace owns (organization screens, a study's own record and state, the design). named: it may touch workspace data and names which workspace itself (in the request, or by what it addresses). required: it acts in the workspace the session has entered (UAT or Live) and a session that has entered none is refused with 409 workspace.required.
  • Refused when. For writes, the study-state intent that must be allowed. An intent is a kind of write: enrol (a new participant); data_entry (new or changed clinical data); cleaning (queries, SDV, signatures, deviations); safety (adverse-event reporting); version_work (starting, editing, submitting or testing a version); metadata (the study's own fields and documents); governance (team and sites). Which states allow which intent is on Change the state of a study.
  • Errors answer with a stable code; see Error messages.

253 operations: 207 ask for a permission (36 of them strictly), 36 need only a signed-in user, 8 need no sign-in, 0 could not be read by the script.

Access exceptions

Time-limited exceptions to the role rules.

OperationWhat it doesWho may callWorkspaceRefused when
GET /platform/access-exceptionsList the organization's scoped access exceptions (member profile, access report)rbac.manage or user.managenone
POST /platform/access-exceptionsRequest an exception: one permission added to or removed from one grant, with a reason and an expiry of at most 90 days (an elevated or critical one waits for a second approver)rbac.managenone
POST /platform/access-exceptions/{exceptionId}/approveApprove a pending exception as the second approver (not the requester, not the member)rbac.managenone
POST /platform/access-exceptions/{exceptionId}/endReject, withdraw or revoke an exception, with a reasonrbac.managenone

Access report

Who holds which role, where.

OperationWhat it doesWho may callWorkspaceRefused when
GET /platform/access-reportThe organization's access report: every open grant (organization role, study and site membership) with the scoped exceptions open on itrbac.manage or user.managenone
GET /platform/access-report/rolesHow many open grants and people each role has (a role nobody holds is absent)rbac.manage or user.managenone

AI

The role assistant and the form generator.

OperationWhat it doesWho may callWorkspaceRefused when
POST /studies/{studyId}/ai/forms/generateGenerate a form IR from a prompt, grounded on the studyai.generatenoneversion_work not allowed
GET /studies/{studyId}/ai/forms/recentRecent AI generations for this studyai.generatenoneversion_work not allowed
POST /platform/library/ai/forms/generateGenerate a library form IR from a promptai.generatenone
GET /platform/library/ai/forms/recentRecent library AI generationsai.generatenone

Audit

Reading the ledger, filtering it and verifying its chain.

OperationWhat it doesWho may callWorkspaceRefused when
GET /platform/auditQuery the append-only audit ledger (newest first)audit.readnone
GET /platform/audit/summaryWhat a trail holds: total, signed, today and past hour, each counted to a cap (the same narrowing as the list)audit.readnone
GET /platform/audit/facetsWhat the trail filters (event, person, subject) can be chosen from: the newest events of the same narrowingaudit.readnone
GET /platform/audit/events/{eventId}One audit entry with its hashes and the length of its chain (what a row of the list view leaves out)audit.readnone
GET /platform/audit/verifyVerify the audit hash chains (admin): in full from each genesis, or quickly from each last checkpointaudit.verifynone
GET /platform/org-auditQuery the organization audit trail (Org Admin only)any signed-in usernone

Casebook

A participant's whole record, and its signature.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/casebookOne page of the subject x visit casebook gridsubject.readrequired
GET /studies/{studyId}/casebook/cellThe forms of one casebook cell, read when it is openedsubject.readrequired

Contacts

Contacts with a participant.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/subjects/{subjectId}/contactsList a subject's contacts, newest first (the removed ones only when asked for)subject.readrequired
POST /studies/{studyId}/subjects/{subjectId}/contactsRecord a contact with the subject (never a visit)clinical.contact.write (no administrator override)requiredcleaning not allowed
POST /studies/{studyId}/subjects/{subjectId}/contacts/{contactId}/removeRemove a mistaken contact, with the reason (it stays on record)clinical.contact.write (no administrator override)requiredcleaning not allowed

Dashboards

Study and portfolio figures.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/portfolio-summaryRead the portfolio rollup for the global dashboardstudy.readnamed
GET /studies/metricsRead rollup metrics for an explicit list of studiesstudy.readnamed
GET /studies/{studyId}/dashboardRead the aggregated dashboard for one studystudy.readnamed

Data capture

Saving and signing the forms of a visit.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/subjects/{subjectId}/recordsList a subject's captured form records (casebook completion); ?view=summary leaves the values outclinical.event.readrequired
GET /studies/{studyId}/subjects/{subjectId}/records/stateWhere each of a subject's records stands (complete, locked, signed), without the recordsclinical.event.readrequired
GET /studies/{studyId}/subjects/{subjectId}/records/{visitInstanceId}/{recordId}Get a single captured form recordclinical.event.readrequired
PUT /studies/{studyId}/subjects/{subjectId}/records/{visitInstanceId}/{recordId}Save a form record's field responses (field-audit + auto-query reconciliation)form.write (no administrator override)requireddata_entry not allowed
POST /studies/{studyId}/subjects/{subjectId}/records/{visitInstanceId}/{recordId}/signSign and lock a completed record (21 CFR Part 11; step-up OTP re-auth)signature.sign (no administrator override)requiredcleaning not allowed
GET /studies/{studyId}/subjects/{subjectId}/events/{eventId}/recordGet the record of an event (its items); the event is on no visitclinical.event.readrequired
PUT /studies/{studyId}/subjects/{subjectId}/events/{eventId}/recordSave the items of an event's record (field-audit + auto-query reconciliation); 409 when the event was removedform.write (no administrator override)required
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/record/signSign and lock the record of an event (21 CFR Part 11; step-up OTP re-auth)signature.sign (no administrator override)requiredcleaning not allowed
GET /studies/{studyId}/subjects/{subjectId}/visits/{visitInstanceId}/signatureWhere a visit stands against its signature: what stands, what a change withdrew, what signing it now would meetclinical.event.readrequired
POST /studies/{studyId}/subjects/{subjectId}/visits/{visitInstanceId}/signSign a visit (21 CFR Part 11; meaning responsibility; step-up OTP re-auth)signature.sign (no administrator override)requiredcleaning not allowed
GET /studies/{studyId}/subjects/{subjectId}/casebook/signatureWhere a subject's casebook stands against its signatureclinical.event.readrequired
POST /studies/{studyId}/subjects/{subjectId}/casebook/signSign a subject's casebook (21 CFR Part 11; meaning approval; step-up OTP re-auth)signature.sign (no administrator override)requiredcleaning not allowed

Documents

The document register and its versions.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/documentsList a study's document registerdocument.readnamedmetadata not allowed
POST /studies/{studyId}/documentsRegister an uploaded documentdocument.writenamedmetadata not allowed
POST /studies/{studyId}/documents/upload-urlCreate a presigned upload URL for a document filedocument.writenamedmetadata not allowed
GET /studies/{studyId}/documents/{documentId}Get a documentdocument.readnamedmetadata not allowed
PATCH /studies/{studyId}/documents/{documentId}Update document metadatadocument.writenamedmetadata not allowed
GET /studies/{studyId}/documents/{documentId}/versionsList a document's version historydocument.readnamedmetadata not allowed
POST /studies/{studyId}/documents/{documentId}/versionsAdd a new version (uploaded file) to a documentdocument.writenamedmetadata not allowed
GET /studies/{studyId}/documents/{documentId}/content-urlGet a presigned download/preview URL (audited)document.readnamedmetadata not allowed
POST /studies/{studyId}/documents/{documentId}/actionApprove / archive a documentdocument.managenamedmetadata not allowed

Exports

Data and design exports.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/exportsList a study's export jobsexport.readnamed
POST /studies/{studyId}/exportsRequest an export (runs the dataset to storage)export.runrequired
POST /studies/{studyId}/exports/designRequest the design of one version: its frozen snapshot, or a draft in Build at a revision (no container: read from either)export.read or export.runnone
GET /studies/{studyId}/exports/{exportId}Get an export jobexport.readnamed
GET /studies/{studyId}/exports/{exportId}/downloadDownload a ready export artifactexport.readnamed

Form library

Reusable forms and visits shared across studies.

OperationWhat it doesWho may callWorkspaceRefused when
GET /platform/library/formsList library formsany signed-in usernone
POST /platform/library/formsCreate a library formlibrary.writenone
GET /platform/library/forms/statsCatalogue-wide counts for library formsany signed-in usernone
GET /platform/library/forms/{id}Get a library formany signed-in usernone
PATCH /platform/library/forms/{id}Update a library formlibrary.writenone
POST /platform/library/forms/{id}/archiveArchive/unarchive a library formlibrary.writenone

Forms

The study's case report forms and their versions.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/formsList a study's formsform.readnoneversion_work not allowed
POST /studies/{studyId}/formsCreate a study form (blank or from a library template)form.design or study.writenoneversion_work not allowed
GET /studies/{studyId}/forms/{formId}Get a study form (with its current document)form.readnoneversion_work not allowed
PATCH /studies/{studyId}/forms/{formId}Save the draft form (eCRF builder autosave of the document)form.design or study.writenoneversion_work not allowed
GET /studies/{studyId}/forms/{formId}/field-dataWhat has been captured for fields of a form, in Live and UAT (the field type control reads it)form.design or study.writenoneversion_work not allowed
GET /studies/{studyId}/forms/{formId}/versionsList a form's version historyform.readnoneversion_work not allowed
POST /studies/{studyId}/forms/{formId}/transitionTransition a form between lifecycle statesform.publish or form.design or study.writenoneversion_work not allowed
POST /studies/{studyId}/forms/{formId}/new-draftBegin a new draft version of a released formform.design or study.writenoneversion_work not allowed

Health and build information

Probes for operators; no clinical data.

OperationWhat it doesWho may callWorkspaceRefused when
GET /_internal/healthDependency health probe (database connectivity + pool)no sign-in needednone
GET /_internal/infoService build/runtime informationno sign-in needednone

Mail

Mail delivery status.

OperationWhat it doesWho may callWorkspaceRefused when
POST /platform/mail/eventsA mail service reports what became of a message: delivered, bounced or complained (HMAC-signed body; no session)no sign-in needednone

Notifications

The in-app bell.

OperationWhat it doesWho may callWorkspaceRefused when
GET /notificationsList the caller's notifications (cursor-paginated)any signed-in usernone
POST /notifications/{notificationId}/readMark one of the caller’s own notifications as readany signed-in usernone

Organization setup

The onboarding wizard and the setup overview.

OperationWhat it doesWho may callWorkspaceRefused when
GET /platform/organization/onboardingWhere the signed-in organization is in its onboarding wizard (org.read)org.readnone
POST /platform/organization/onboarding/team/completeThe team step is done: people were invited (org.manage)org.managenone
POST /platform/organization/onboarding/team/skipThe team step is skipped (org.manage)org.managenone
POST /platform/organization/onboarding/finishThe wizard is finished: the organization lands on its dashboard (org.manage)org.managenone
GET /platform/organization/overviewWhat the organization dashboard shows before it has got going (org.read)org.readnone

Organizations

The caller's own organization and its provisioning.

OperationWhat it doesWho may callWorkspaceRefused when
GET /platform/organizations/currentThe caller's own tenant organizationany signed-in usernone
GET /platform/organizationsList organizations (admin)org.readnone
GET /platform/organizations/current/governance-summaryOrg-governance counts for the Organization Admin dashboardorg.readnone
GET /platform/organizations/{orgId}Get an organization by id (admin)org.readnone
PATCH /platform/organizations/{orgId}Update an organization (admin)org.managenone
POST /platform/organizations/{orgId}/suspendSuspend an organization (admin)org.managenone
POST /platform/organizations/{orgId}/restoreRestore an organization (admin)org.managenone

Participants

Screening, eligibility, enrollment and status changes.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/subjects/summaryThe counts a subjects page draws (by status, eligibility and site), under the reader's site scope, without the rostersubject.readrequired
GET /studies/{studyId}/subjectsList a study's subjects (offset-paginated table page)study.version.readrequired
POST /studies/{studyId}/subjectsScreen (intake) a subjectsubject.write (no administrator override)requiredenrol not allowed
GET /studies/{studyId}/subjects/{subjectId}Get a subjectclinical.event.readrequired
GET /studies/{studyId}/subjects/{subjectId}/visitsList a subject's materialized visit instancessubject.readrequired
GET /studies/{studyId}/subjects/{subjectId}/open-itemsWhat is still open for a subject: queries, forms not yet signed, follow-up reports still duesubject.readrequired
POST /studies/{studyId}/subjects/{subjectId}/eligibilityDetermine a subject's eligibility (investigator attestation)subject.eligibility (no administrator override)requireddata_entry not allowed
POST /studies/{studyId}/subjects/{subjectId}/enrollEnroll a subject (assigns subject number + baseline, materializes visits)subject.write (no administrator override)requiredenrol not allowed
POST /studies/{studyId}/subjects/{subjectId}/transitionTransition a subject (randomize / activate / screen_fail / withdraw / lost_to_follow_up / complete)subject.write or subject.deleterequireddata_entry not allowed
POST /studies/{studyId}/subjects/{subjectId}/withdrawWithdraw a subject (reason required)subject.delete (no administrator override)requireddata_entry not allowed
POST /studies/{studyId}/subjects/{subjectId}/safety-follow-up/completeComplete the safety follow-up a withdrawal said was owed (the note says what was done)clinical.event.report (no administrator override)requiredsafety not allowed
POST /studies/{studyId}/subjects/{subjectId}/unscheduled-visitCreate an unscheduled visit instance (events found at it are recorded as events, with the visit as where)subject.write (no administrator override)requireddata_entry not allowed
POST /studies/{studyId}/subjects/{subjectId}/visits/{visitInstanceId}/early-accessPI/CRC records an audited exception to enter data before a visit's window openssubject.write (no administrator override)requireddata_entry not allowed
POST /studies/{studyId}/subjects/{subjectId}/visits/{visitInstanceId}/modeSay how a visit was held, on site or remotely (phone, video or at the subject's home), with the reasonsubject.write (no administrator override)requireddata_entry not allowed
GET /studies/{studyId}/subjects/{subjectId}/reconsentsWhat the subject answered to amendments that ask them to consent again, newest first, and what still asks themsubject.readrequired
POST /studies/{studyId}/subjects/{subjectId}/reconsentsRecord a subject’s consent, or refusal, when an amendment asks them to consent againclinical.consent.write (no administrator override)requiredcleaning not allowed

People

Members of the organization, invitations, suspension and removal.

OperationWhat it doesWho may callWorkspaceRefused when
GET /platform/usersList org users (admin)user.directory.readnone
POST /platform/usersInvite a new org user (admin)user.managenone
GET /platform/users/{userId}Get an org user (admin)user.directory.readnone
PATCH /platform/users/{userId}Update an org user (admin)user.managenone
POST /platform/users/{userId}/suspendSuspend an org user (admin)user.managenone
POST /platform/users/{userId}/restoreRestore an org user (admin)user.managenone
POST /platform/users/{userId}/end-membershipRemove a person from the organization (admin): ends their membership and, with it, their study and site memberships, role grants and every live session in the organizationuser.managenone
POST /platform/users/{userId}/grant-org-adminGrant Organization Admin to an existing org member (admin, self-service co-admin)user.managenone
POST /platform/users/invitations/bulkInvite a team: up to 200 rows, each judged and sent on its own (admin)user.managenone
GET /platform/users/invitationsThe organization's invitations with the status of each one's mail (admin)user.managenone
POST /platform/users/invitations/{invitationId}/resendQueue a fresh mail for one pending invitation (admin)user.managenone
POST /platform/users/invitations/{invitationId}/revokeRevoke an invitation that has not been answered (admin)user.managenone

Protocol deviations

Reporting and reviewing deviations.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/deviationsList a study's protocol deviations (offset-paginated table page)clinical.query.readrequiredcleaning not allowed
POST /studies/{studyId}/deviationsReport a protocol deviationdeviation.report (no administrator override)requiredcleaning not allowed
PATCH /studies/{studyId}/deviations/{deviationId}/statusUpdate a protocol deviation review statusdeviation.report (no administrator override)requiredcleaning not allowed

Queries

Raising, answering, closing, reopening and escalating queries.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/queriesList a study's queries (offset-paginated table page)clinical.query.readrequired
POST /studies/{studyId}/queriesRaise a queryclinical.query.raise (no administrator override)requiredcleaning not allowed
GET /studies/{studyId}/queries/statsQuery KPI counts for the study (open / overdue / auto / manual)clinical.query.readrequired
GET /studies/{studyId}/queries/{queryId}Get a query with its threadclinical.query.readrequired
POST /studies/{studyId}/queries/{queryId}/answerAnswer a query (the site responds; not the person who raised it: 409 clinical.separation_of_duties)clinical.query.answer (no administrator override)requiredcleaning not allowed
POST /studies/{studyId}/queries/{queryId}/transitionClose / reopen / escalate a query (not a close by the person who answered it: 409 clinical.separation_of_duties)depends on the action: see the pagerequiredcleaning not allowed

Reports

Enrollment, sites, query aging, visit compliance, deviations, lock readiness.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/reports/enrollmentEnrollment report of one studysubject.readrequired
GET /studies/{studyId}/reports/queriesQuery-aging report of one studyclinical.query.readrequired
GET /studies/{studyId}/reports/sitesSite-performance report of one studysite.readrequired
GET /studies/{studyId}/reports/site-performanceEach site of one study against its target, with what is open and verified, and a risksite.readrequired
GET /studies/{studyId}/reports/lock-readinessWhat stands between one study and database lock, by sitestudy.readrequired
GET /studies/{studyId}/reports/deviationsProtocol-deviation report of one studyclinical.query.readrequired
GET /reports/enrollmentCross-study enrollment reportsubject.readnamed
GET /reports/queriesCross-study query-aging reportclinical.query.readnamed
GET /reports/site-performanceEach site against its target, with what is open and verified, and a risksite.readnamed
GET /reports/sitesCross-study site-performance reportsite.readnamed
GET /reports/deviationsCross-study protocol-deviation reportclinical.query.readnamed
GET /reports/signaturesCross-study e-signature manifestaudit.readnamed

Roles

The standard roles, custom roles, their revisions and risk assessment.

OperationWhat it doesWho may callWorkspaceRefused when
GET /platform/rbac/permissionsThe permission catalogany signed-in usernone
GET /platform/rbac/rolesThe role catalog (system + custom), or with assignable=true what a picker offersany signed-in usernone
POST /platform/rbac/rolesCreate a custom roleany signed-in usernone
GET /platform/rbac/roles/{roleId}Get a role by idany signed-in usernone
PATCH /platform/rbac/roles/{roleId}Update a custom roleany signed-in usernone
POST /platform/rbac/roles/assessDry-run the role guardrails (writes nothing)any signed-in usernone
GET /platform/rbac/roles/{roleId}/revisionsA custom role's history, oldest firstany signed-in usernone
POST /platform/rbac/roles/{roleId}/suspendSuspend a custom roleany signed-in usernone
POST /platform/rbac/roles/{roleId}/restoreRestore a custom roleany signed-in usernone
GET /platform/rbac/templatesThe standard roles as the onboarding wizard's starting points, grouped by where a role holds its permissionsany signed-in usernone
POST /platform/rbac/roles/assess-setJudge every role of a set together: the review screen (writes nothing)any signed-in usernone
POST /platform/rbac/roles/bulkCreate a set of roles, all or none, each as revision 1 (the onboarding wizard's second step)any signed-in usernone
POST /platform/rbac/ai/suggest-permissionsSuggest the permissions that fit a role (never applied)any signed-in usernone
POST /platform/rbac/ai/explain-warningExplain a warning the role guardrails raised, in wordsany signed-in usernone

Rules

Cross-form rules.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/rulesList a study's cross-form rulesworkflow.design or study.write or form.readnoneversion_work not allowed
POST /studies/{studyId}/rulesCreate a cross-form ruleworkflow.design or study.writenoneversion_work not allowed
GET /studies/{studyId}/rules/{ruleId}Get a ruleworkflow.design or study.write or form.readnoneversion_work not allowed
PATCH /studies/{studyId}/rules/{ruleId}Update a ruleworkflow.design or study.writenoneversion_work not allowed
POST /studies/{studyId}/rules/{ruleId}/statusActivate / disable / retire a ruleworkflow.design or study.writenoneversion_work not allowed

Safety events

Adverse events, serious events, medical review, reconciliation and follow-up.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/subjects/{subjectId}/eventsList a subject's events (the removed ones only when asked for)clinical.event.readrequired
POST /studies/{studyId}/subjects/{subjectId}/eventsRecord an event (an adverse event, a concomitant medication); the form says what kind it isclinical.event.write (no administrator override)required
GET /studies/{studyId}/subjects/{subjectId}/events/{eventId}Get an eventclinical.event.readrequired
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/upgrade-to-saeUpgrade an adverse event to a serious one: a new event linked to it, with the report and the version's reporting clockclinical.event.write (no administrator override)required
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/reportSet the safety-report status of a serious event (expedited reporting, ICH E2A); a report that was sent is finalclinical.event.report (no administrator override)required
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/awarenessCorrect when the site became aware of a serious event, with the reason; the due time is computed againclinical.event.report (no administrator override)required
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/statusOpen or close an event (ongoing, or ended)clinical.event.write (no administrator override)required
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/removeRemove a mistaken event, with the reason (it stays on record, with its number)clinical.event.write (no administrator override)required
GET /studies/{studyId}/subjects/{subjectId}/events/{eventId}/follow-upsList the follow-up reports of a serious event, by number (the removed ones only when asked for)clinical.event.readrequired
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/follow-upsRecord a follow-up report of a serious event: when the site learned the new information starts its clock (the version's follow-up window)clinical.event.report (no administrator override)required
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/follow-ups/{followUpId}/statusSet the status of a follow-up report (pending, sent, not required); a follow-up that was sent is finalclinical.event.report (no administrator override)required
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/follow-ups/{followUpId}/removeRemove a mistaken follow-up report, with the reason (it stays on record, with its number); a sent one cannot be removedclinical.event.report (no administrator override)required
GET /studies/{studyId}/subjects/{subjectId}/events/{eventId}/reviewsList the medical reviews of a serious event in the order written; the last stands. The study team's: site staff are refused (403)clinical.event.readrequired
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/reviewsRecord the medical review of a serious event (agreed, disagreed, or needs information; a comment unless agreed). Append-only; not by the person who recorded the event (409 clinical.separation_of_duties)clinical.event.review (no administrator override)required
GET /studies/{studyId}/subjects/{subjectId}/events/{eventId}/reconciliationsList the reconciliations of a serious event with the safety database in the order written; the last stands. The study team's: site staff are refused (403)clinical.event.readrequired
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/reconciliationsRecord the reconciliation of a serious event with the safety database (matched, with the safety database's case number; or a discrepancy, with a note). Append-onlyclinical.event.reconcile (no administrator override)required

Safety logs

The AE and SAE logs.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/safety/ae-logThe AE log: one entry per adverse event, in the session's workspace and at the reader's sitesclinical.event.readrequired
GET /studies/{studyId}/safety/sae-logThe SAE log: one entry per serious adverse event, with its report, its clock and the adverse event it was upgraded fromclinical.event.readrequired
GET /studies/{studyId}/safety/med-logThe concomitant medications: one entry per medication event, in the session's workspace and at the reader's sitesclinical.event.readrequired
GET /studies/{studyId}/safety/other-eventsThe other events: one entry per event of an "other" event form, with the first few data fields of that formclinical.event.readrequired

Signatures

The register of electronic signatures.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/signaturesList a study's applied signatures (the manifest)audit.readrequired
GET /studies/{studyId}/signatures/{signatureId}Get an applied signatureaudit.readrequired

Signing in and sessions

One-time codes, the choice of organization and workspace, tokens and your own sessions.

OperationWhat it doesWho may callWorkspaceRefused when
POST /auth/otp/requestRequest a one-time code — to sign in, or to signNo sign-in needed to ask for a sign-in code; asking for a signing code needs a signed-in sessionnone
POST /auth/otp/verifyVerify an OTP — completes login, or asks which organization to sign in tono sign-in needednone
POST /auth/organisation/selectChoose the organization to sign in to (people who belong to several)no sign-in needednone
POST /auth/workspaceEnter a version workspace (UAT or Live), or leave it — a new session for the same sign-inany signed-in usernone
POST /auth/refreshExchange a refresh token for a fresh token pairno sign-in needednone
POST /auth/logoutRevoke the current sessionany signed-in usernone
GET /auth/meThe current user, active role, and available rolesany signed-in usernone
GET /auth/sessionsThe caller's own active/recent sessions (device management)any signed-in usernone
POST /auth/sessions/{sessionId}/revokeRevoke one of the caller's OWN sessions (not the current one — use /auth/logout for that)any signed-in usernone
POST /auth/invitation/openWhat an invitation link says: the organization, and while it is valid the address to sign in with (no session; not consumed)no sign-in needednone
POST /auth/invitation/resendA person whose link expired asks for a fresh one, by address (no session; answered the same whoever is invited)no sign-in needednone

Sites

Registering, activating and closing sites; their staff and delegations.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/sitesList a study's sitessite.readnamed
POST /studies/{studyId}/sitesRegister a new sitesite.writerequiredgovernance not allowed
GET /studies/{studyId}/sites/{siteId}Get a sitesite.readrequired
POST /studies/{studyId}/sites/{siteId}/activateActivate a site (IRB approval + training gate)site.activaterequiredgovernance not allowed
PATCH /studies/{studyId}/sites/{siteId}/targetSet a site's enrollment target (or clear it with null)site.writerequiredgovernance not allowed
POST /studies/{studyId}/sites/{siteId}/transitionSuspend / resume / close a sitesite.transitionrequiredgovernance not allowed

Source data verification

The SDV plan and the monitor's queue.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/sdv/planList the active SDV targeting planany signed-in userrequired
POST /studies/{studyId}/sdv/planAdd or update a SDV requirement (risk-based targeting)any signed-in userrequiredcleaning not allowed
DELETE /studies/{studyId}/sdv/plan/{itemId}Retire a SDV requirement (soft-delete)any signed-in userrequiredcleaning not allowed
GET /studies/{studyId}/sdv/subjectsEvery subject SDV status in one read (monitoring dashboard)any signed-in userrequired
GET /studies/{studyId}/sdv/subjects/{subjectId}Read a subject's SDV plan + live datapoint verification statusany signed-in userrequired
POST /studies/{studyId}/sdv/subjects/{subjectId}/verifyRecord a source-data-verification action on a datapointany signed-in userrequiredcleaning not allowed

Studies

Creating a study, its state, lock and archive.

OperationWhat it doesWho may callWorkspaceRefused when
PATCH /studies/{studyId}Update persisted study metadatastudy.writenonemetadata not allowed
GET /studies/{studyId}Get a study by idany signed-in usernone
GET /studies/aggregates/complianceRead portfolio visit-compliance report (Reports > Visit Compliance)study.readnamed
GET /studies/{studyId}/reports/complianceRead the visit-compliance report of one studysite.readrequired
GET /studiesList studies (portfolio)study.readnone
POST /studiesCreate a study (admin / PM)study.createnone
GET /studies/{studyId}/summaryCross-module aggregate metrics (enrollment, queries, visit compliance, sites)any signed-in userrequired
GET /studies/{studyId}/stateThe study's operational state, the actions the caller may take, the lock request waiting for a second person, and the pre-lock listingany signed-in usernone
POST /studies/{studyId}/stateChange the study state (reason required; reachable from either workspace). hold, resume, close_enrolment, complete and archive answer the updated study. lock and unlock take two different people with e-signatures: the first call (readinessHash and signature) initiates and answers 202, the second, by a different person, confirms and answers 200.any signed-in usernone
POST /studies/{studyId}/state/request/cancelCancel the pending lock or unlock request (reason required)study.lock (no administrator override)none

Study team

Who holds a role on the study, and site staff delegations.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/teamList a study's role assignmentsuser.manage or study.write or site.readnamedgovernance not allowed
POST /studies/{studyId}/teamProvision a role assignment (site staff stay pending PI delegation)team.assignnamedgovernance not allowed
GET /studies/{studyId}/team/sitesList a study's sites in UAT and in Live, for the team's site tabsuser.manage or study.write or site.readnamedgovernance not allowed
GET /studies/{studyId}/team/{assignmentId}Get a role assignmentuser.manage or study.write or site.readnamedgovernance not allowed
POST /studies/{studyId}/team/{assignmentId}/delegatePI signs the delegation-log entry waiting for them — e-signed (21 CFR 11.50)delegation.sign (no administrator override)namedgovernance not allowed
GET /studies/{studyId}/team/{assignmentId}/delegationsA site member's delegation log: every entry, including those that ended, with the names of who proposed, signed and ended eachuser.manage or study.write or site.readnamedgovernance not allowed
POST /studies/{studyId}/team/{assignmentId}/delegationsPropose an amendment of a site member's delegated tasks and dates, for the PI to signteam.assign or delegation.signnamedgovernance not allowed
GET /studies/{studyId}/team/{assignmentId}/delegable-tasksThe tasks a delegation of this site member can name: those their role coversteam.assign or delegation.signnamedgovernance not allowed
POST /studies/{studyId}/team/{assignmentId}/delegations/{delegationId}/endWithdraw a proposed delegation entry, or end a signed one before its end dateteam.assign or delegation.signnamedgovernance not allowed
POST /studies/{studyId}/team/{assignmentId}/manageSuspend / resume / retire / complete-training an assignmentteam.training_complete or team.managenamedgovernance not allowed

Versions

The design's life: build, UAT cycles, sign-offs, go live, amendments and re-consent.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/versionsList a study's versions, newest firststudy.version.readnoneversion_work not allowed
POST /studies/{studyId}/versionsStart an amendment (the next version of a Live study)study.amendment.startnoneversion_work not allowed
GET /studies/{studyId}/versions/{versionId}Get a version with its cycles, sign-offs and amendmentstudy.version.readnoneversion_work not allowed
PATCH /studies/{studyId}/versions/{versionId}Edit the summary and settings of a version in Buildstudy.writenoneversion_work not allowed
GET /studies/{studyId}/versions/{versionId}/snapshots/{snapshotId}The compiled snapshot a UAT cycle tested, with its hashesstudy.version.readnoneversion_work not allowed
GET /studies/{studyId}/versions/{versionId}/cyclesThe UAT cycles of a version, with the test data each holdsstudy.version.readnamedversion_work not allowed
GET /studies/{studyId}/versions/{versionId}/cycles/{number}One UAT cycle of a version by its numberstudy.version.readnamedversion_work not allowed
GET /studies/{studyId}/versions/{versionId}/revisionsThe draft history of a version, newest first, a page at a timestudy.version.readnoneversion_work not allowed
GET /studies/{studyId}/versions/{versionId}/buildWhat a version’s Build stands atstudy.version.readnoneversion_work not allowed
GET /studies/{studyId}/versions/{versionId}/go-liveWhat stands between a version and Livestudy.version.readnoneversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/go-liveTake an approved version Livestudy.version.go_livenoneversion_work not allowed
GET /studies/{studyId}/versions/{versionId}/changesWhat an amendment changed against the Live version it started fromstudy.version.readnoneversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/submit-to-uatSubmit a version in Build to UAT: publish its forms, compile the snapshot, open a cyclestudy.version.submit_uatnoneversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/passPass the open UAT cyclestudy.uat.close_cyclenamedversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/failFail the open UAT cycle (back to Build)study.uat.close_cyclenamedversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/signoffSign off (or reject) a version that passed UAT; the label is derived from the signer’s rolestudy.version.signoff (no administrator override)noneversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/withdrawWithdraw a version that is not yet Live (reason required)study.version.withdrawnoneversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/transition-subjectsMove the study’s Live subjects to this version (the one that is Live): its schedule, and the future visits of the old one that nothing was done to, retiredstudy.version.go_livenamedversion_work not allowed
GET /studies/{studyId}/versions/{versionId}/cycles/{number}/issuesThe issues of one UAT cycle, newest first, with their countsstudy.uat_issue.readnamedversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/issuesRaise an issue in the open UAT cyclestudy.uat_issue.writenamedversion_work not allowed
PATCH /studies/{studyId}/versions/{versionId}/cycles/{number}/issues/{issueNumber}Edit the title, description or severity of an issue of the open UAT cyclestudy.uat_issue.writenamedversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/issues/{issueNumber}/resolveResolve an issue: settled (not a defect, cleared, or answered); a note may say howstudy.uat_issue.writenamedversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/issues/{issueNumber}/acceptAccept an issue: a known limitation taken knowingly; the reason is requiredstudy.uat_issue.writenamedversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/issues/{issueNumber}/reopenReopen a resolved or accepted issuestudy.uat_issue.writenamedversion_work not allowed
GET /studies/{studyId}/versions/{versionId}/roundsThe rounds of a version's Build: what each was asked, what its saves did, how it endedstudy.uat_issue.readnamedversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/corrections/{issueNumber}/resolveMark a finding of the latest failed UAT cycle resolved in the design; a note may say what was doneform.designnamedversion_work not allowed
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/corrections/{issueNumber}/reopenTake back a finding marked resolvedform.designnamedversion_work not allowed
GET /studies/{studyId}/versions/{versionId}/reconsentWhere the study’s Live subjects stand against an amendment that asks them to consent againstudy.version.readrequired
GET /studies/{studyId}/versions/{versionId}/affected-subjectsWhich Live subjects an amendment will move when it goes Live, and which will wait for a re-consentstudy.version.readnamed

Visit and form mapping

Which forms are collected at which visits.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/mappingList the form↔visit mapping matrixform.read or visit.read or form.design or study.writenoneversion_work not allowed
POST /studies/{studyId}/mappingBind a form to a visit (upsert a matrix cell; a cell that is already bound is changed, so it needs baseRevision)form.design or study.writenoneversion_work not allowed
GET /studies/{studyId}/mapping/{mappingId}Get a mapping cellform.read or visit.read or form.design or study.writenoneversion_work not allowed
PATCH /studies/{studyId}/mapping/{mappingId}Update a mapping cell (required/condition/order/state)form.design or study.writenoneversion_work not allowed
DELETE /studies/{studyId}/mapping/{mappingId}Unbind a form from a visitform.design or study.writenoneversion_work not allowed

Visit schedule

Protocol visits and windows.

OperationWhat it doesWho may callWorkspaceRefused when
GET /studies/{studyId}/visitsList a study's visit schedulevisit.readnoneversion_work not allowed
POST /studies/{studyId}/visitsAdd a visit to the scheduleform.design or study.writenoneversion_work not allowed
GET /studies/{studyId}/visits/{visitId}Get a visitvisit.readnoneversion_work not allowed
PATCH /studies/{studyId}/visits/{visitId}Update a visitform.design or study.writenoneversion_work not allowed
POST /studies/{studyId}/visits/{visitId}/statusActivate or archive a visitform.design or study.writenoneversion_work not allowed

The platform console's API

The control plane has its own, much smaller API under /api/platform, used only by the platform console. Every operation needs a signed-in platform administrator (a Super-Admin) except asking for and verifying a code and renewing a session; there is no clinical data on it. Sign-in requests are limited to 20 in 15 minutes from one address, and the rest to 240 a minute.

OperationWhat it doesWho may call
GET /api/platform/adminsLists the Super-Admins with their state (invited, active, suspended)a signed-in Super-Admin
POST /api/platform/adminsInvites a Super-Admin and mails the invitationa signed-in Super-Admin
POST /api/platform/admins/:adminId/suspendSuspends a Super-Admin and ends their sessions (not yourself, not the last active one)a signed-in Super-Admin
POST /api/platform/admins/:adminId/restoreRestores a suspended Super-Admina signed-in Super-Admin
GET /api/platform/auditReads the platform ledger, 50 to 200 at a time, newest first, filtered by action prefix or targeta signed-in Super-Admin
GET /api/platform/organizationsLists the organizationsa signed-in Super-Admin
PATCH /api/platform/organizations/:orgIdCorrects an organization's name, code, type, country or first administrator, with a reasona signed-in Super-Admin
POST /api/platform/organizationsProvisions an organization and pushes it to the data plane, with its first Organization Admina signed-in Super-Admin
POST /api/platform/organizations/:orgId/syncPushes an organization to the data plane again (idempotent)a signed-in Super-Admin
GET /api/platform/organizations/:orgIdReads one organizationa signed-in Super-Admin
GET /api/platform/organizations/:orgId/admin-invitationsReads the first administrators' invitations from the data planea signed-in Super-Admin
POST /api/platform/organizations/:orgId/admin-invitations/resendSends the pending first-administrator invitation againa signed-in Super-Admin
POST /api/platform/organizations/:orgId/suspendSuspends an organization (a reason of at least five characters); the data plane is told firsta signed-in Super-Admin
POST /api/platform/organizations/:orgId/restoreRestores a suspended organization (the reason is optional)a signed-in Super-Admin
POST /api/platform/organizations/:orgId/closeCloses an organization for good (a reason); the data plane treats it as suspendeda signed-in Super-Admin
GET /api/platform/organizations/:orgId/usageReads live counts from the data plane (users, studies, exports, stored bytes)a signed-in Super-Admin
GET /api/platform/organizations/:orgId/entitlementsLists the five feature switches of an organizationa signed-in Super-Admin, or the data plane with its narrow key
PUT /api/platform/organizations/:orgId/entitlements/:featureKeySwitches one feature on or off (not sso or api.access)a signed-in Super-Admin
GET /api/platform/statsCounts organizations by statusa signed-in Super-Admin
GET /api/platform/stats/data-planeAsks the data plane about every pushed organization: invitations and usagea signed-in Super-Admin
POST /api/platform/auth/otp/requestAsks for a six-digit sign-in code (answers the same for any address)no sign-in needed
POST /api/platform/auth/otp/verifyExchanges the code for a sessionno sign-in needed
POST /api/platform/auth/refreshRenews a session from its refresh tokenno sign-in needed (the refresh token)
POST /api/platform/auth/logoutEnds the caller's own sessiona signed-in Super-Admin
GET /api/platform/auth/meWho the caller isa signed-in Super-Admin

Compliance Q&A

Compliance Q&ABuiltPart 11 11.10(d)Part 11 11.10(g)Annex 11 12ICH E6 data-governance
Is every operation behind an access check, or can a script do what a screen refuses?

The screens call this API and nothing else, so the rules are the same for a script. 207 of the 253 operations ask for a permission, 36 of them with no Organization Admin override; 36 need only a signed-in user (reading one's own session, notifications and similar); 8 need no sign-in, and those are limited to asking for a code, refreshing a session and the health probes. A role that resolves to no permissions is refused, never let through.

Evidence: server/src/api/routes/index.tseach module's interface/routes.tsserver/src/api/middleware/authorize.tsthe route contract tests

Compliance Q&ANot built yetAnnex 11 5
Is there a public API for integrations, with its own keys and limits?

No. The API above is the application's own and is reached with the session of a signed-in person. A separate integration API with keys is not built: the api.access feature switch exists in the platform console but gates nothing and is shown as Not built yet. Data leaves the system through exports.

Roles and permissions · Error messages · Audit events · API and integrations · Architecture