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 with409 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.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /platform/access-exceptions | List the organization's scoped access exceptions (member profile, access report) | rbac.manage or user.manage | none | |
POST /platform/access-exceptions | Request 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.manage | none | |
POST /platform/access-exceptions/{exceptionId}/approve | Approve a pending exception as the second approver (not the requester, not the member) | rbac.manage | none | |
POST /platform/access-exceptions/{exceptionId}/end | Reject, withdraw or revoke an exception, with a reason | rbac.manage | none |
Access report
Who holds which role, where.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /platform/access-report | The organization's access report: every open grant (organization role, study and site membership) with the scoped exceptions open on it | rbac.manage or user.manage | none | |
GET /platform/access-report/roles | How many open grants and people each role has (a role nobody holds is absent) | rbac.manage or user.manage | none |
AI
The role assistant and the form generator.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
POST /studies/{studyId}/ai/forms/generate | Generate a form IR from a prompt, grounded on the study | ai.generate | none | version_work not allowed |
GET /studies/{studyId}/ai/forms/recent | Recent AI generations for this study | ai.generate | none | version_work not allowed |
POST /platform/library/ai/forms/generate | Generate a library form IR from a prompt | ai.generate | none | |
GET /platform/library/ai/forms/recent | Recent library AI generations | ai.generate | none |
Audit
Reading the ledger, filtering it and verifying its chain.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /platform/audit | Query the append-only audit ledger (newest first) | audit.read | none | |
GET /platform/audit/summary | What a trail holds: total, signed, today and past hour, each counted to a cap (the same narrowing as the list) | audit.read | none | |
GET /platform/audit/facets | What the trail filters (event, person, subject) can be chosen from: the newest events of the same narrowing | audit.read | none | |
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.read | none | |
GET /platform/audit/verify | Verify the audit hash chains (admin): in full from each genesis, or quickly from each last checkpoint | audit.verify | none | |
GET /platform/org-audit | Query the organization audit trail (Org Admin only) | any signed-in user | none |
Casebook
A participant's whole record, and its signature.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/casebook | One page of the subject x visit casebook grid | subject.read | required | |
GET /studies/{studyId}/casebook/cell | The forms of one casebook cell, read when it is opened | subject.read | required |
Contacts
Contacts with a participant.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/subjects/{subjectId}/contacts | List a subject's contacts, newest first (the removed ones only when asked for) | subject.read | required | |
POST /studies/{studyId}/subjects/{subjectId}/contacts | Record a contact with the subject (never a visit) | clinical.contact.write (no administrator override) | required | cleaning not allowed |
POST /studies/{studyId}/subjects/{subjectId}/contacts/{contactId}/remove | Remove a mistaken contact, with the reason (it stays on record) | clinical.contact.write (no administrator override) | required | cleaning not allowed |
Dashboards
Study and portfolio figures.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/portfolio-summary | Read the portfolio rollup for the global dashboard | study.read | named | |
GET /studies/metrics | Read rollup metrics for an explicit list of studies | study.read | named | |
GET /studies/{studyId}/dashboard | Read the aggregated dashboard for one study | study.read | named |
Data capture
Saving and signing the forms of a visit.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/subjects/{subjectId}/records | List a subject's captured form records (casebook completion); ?view=summary leaves the values out | clinical.event.read | required | |
GET /studies/{studyId}/subjects/{subjectId}/records/state | Where each of a subject's records stands (complete, locked, signed), without the records | clinical.event.read | required | |
GET /studies/{studyId}/subjects/{subjectId}/records/{visitInstanceId}/{recordId} | Get a single captured form record | clinical.event.read | required | |
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) | required | data_entry not allowed |
POST /studies/{studyId}/subjects/{subjectId}/records/{visitInstanceId}/{recordId}/sign | Sign and lock a completed record (21 CFR Part 11; step-up OTP re-auth) | signature.sign (no administrator override) | required | cleaning not allowed |
GET /studies/{studyId}/subjects/{subjectId}/events/{eventId}/record | Get the record of an event (its items); the event is on no visit | clinical.event.read | required | |
PUT /studies/{studyId}/subjects/{subjectId}/events/{eventId}/record | Save the items of an event's record (field-audit + auto-query reconciliation); 409 when the event was removed | form.write (no administrator override) | required | |
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/record/sign | Sign and lock the record of an event (21 CFR Part 11; step-up OTP re-auth) | signature.sign (no administrator override) | required | cleaning not allowed |
GET /studies/{studyId}/subjects/{subjectId}/visits/{visitInstanceId}/signature | Where a visit stands against its signature: what stands, what a change withdrew, what signing it now would meet | clinical.event.read | required | |
POST /studies/{studyId}/subjects/{subjectId}/visits/{visitInstanceId}/sign | Sign a visit (21 CFR Part 11; meaning responsibility; step-up OTP re-auth) | signature.sign (no administrator override) | required | cleaning not allowed |
GET /studies/{studyId}/subjects/{subjectId}/casebook/signature | Where a subject's casebook stands against its signature | clinical.event.read | required | |
POST /studies/{studyId}/subjects/{subjectId}/casebook/sign | Sign a subject's casebook (21 CFR Part 11; meaning approval; step-up OTP re-auth) | signature.sign (no administrator override) | required | cleaning not allowed |
Documents
The document register and its versions.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/documents | List a study's document register | document.read | named | metadata not allowed |
POST /studies/{studyId}/documents | Register an uploaded document | document.write | named | metadata not allowed |
POST /studies/{studyId}/documents/upload-url | Create a presigned upload URL for a document file | document.write | named | metadata not allowed |
GET /studies/{studyId}/documents/{documentId} | Get a document | document.read | named | metadata not allowed |
PATCH /studies/{studyId}/documents/{documentId} | Update document metadata | document.write | named | metadata not allowed |
GET /studies/{studyId}/documents/{documentId}/versions | List a document's version history | document.read | named | metadata not allowed |
POST /studies/{studyId}/documents/{documentId}/versions | Add a new version (uploaded file) to a document | document.write | named | metadata not allowed |
GET /studies/{studyId}/documents/{documentId}/content-url | Get a presigned download/preview URL (audited) | document.read | named | metadata not allowed |
POST /studies/{studyId}/documents/{documentId}/action | Approve / archive a document | document.manage | named | metadata not allowed |
Exports
Data and design exports.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/exports | List a study's export jobs | export.read | named | |
POST /studies/{studyId}/exports | Request an export (runs the dataset to storage) | export.run | required | |
POST /studies/{studyId}/exports/design | Request 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.run | none | |
GET /studies/{studyId}/exports/{exportId} | Get an export job | export.read | named | |
GET /studies/{studyId}/exports/{exportId}/download | Download a ready export artifact | export.read | named |
Form library
Reusable forms and visits shared across studies.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /platform/library/forms | List library forms | any signed-in user | none | |
POST /platform/library/forms | Create a library form | library.write | none | |
GET /platform/library/forms/stats | Catalogue-wide counts for library forms | any signed-in user | none | |
GET /platform/library/forms/{id} | Get a library form | any signed-in user | none | |
PATCH /platform/library/forms/{id} | Update a library form | library.write | none | |
POST /platform/library/forms/{id}/archive | Archive/unarchive a library form | library.write | none |
Forms
The study's case report forms and their versions.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/forms | List a study's forms | form.read | none | version_work not allowed |
POST /studies/{studyId}/forms | Create a study form (blank or from a library template) | form.design or study.write | none | version_work not allowed |
GET /studies/{studyId}/forms/{formId} | Get a study form (with its current document) | form.read | none | version_work not allowed |
PATCH /studies/{studyId}/forms/{formId} | Save the draft form (eCRF builder autosave of the document) | form.design or study.write | none | version_work not allowed |
GET /studies/{studyId}/forms/{formId}/field-data | What has been captured for fields of a form, in Live and UAT (the field type control reads it) | form.design or study.write | none | version_work not allowed |
GET /studies/{studyId}/forms/{formId}/versions | List a form's version history | form.read | none | version_work not allowed |
POST /studies/{studyId}/forms/{formId}/transition | Transition a form between lifecycle states | form.publish or form.design or study.write | none | version_work not allowed |
POST /studies/{studyId}/forms/{formId}/new-draft | Begin a new draft version of a released form | form.design or study.write | none | version_work not allowed |
Health and build information
Probes for operators; no clinical data.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /_internal/health | Dependency health probe (database connectivity + pool) | no sign-in needed | none | |
GET /_internal/info | Service build/runtime information | no sign-in needed | none |
Mail delivery status.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
POST /platform/mail/events | A mail service reports what became of a message: delivered, bounced or complained (HMAC-signed body; no session) | no sign-in needed | none |
Notifications
The in-app bell.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /notifications | List the caller's notifications (cursor-paginated) | any signed-in user | none | |
POST /notifications/{notificationId}/read | Mark one of the caller’s own notifications as read | any signed-in user | none |
Organization setup
The onboarding wizard and the setup overview.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /platform/organization/onboarding | Where the signed-in organization is in its onboarding wizard (org.read) | org.read | none | |
POST /platform/organization/onboarding/team/complete | The team step is done: people were invited (org.manage) | org.manage | none | |
POST /platform/organization/onboarding/team/skip | The team step is skipped (org.manage) | org.manage | none | |
POST /platform/organization/onboarding/finish | The wizard is finished: the organization lands on its dashboard (org.manage) | org.manage | none | |
GET /platform/organization/overview | What the organization dashboard shows before it has got going (org.read) | org.read | none |
Organizations
The caller's own organization and its provisioning.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /platform/organizations/current | The caller's own tenant organization | any signed-in user | none | |
GET /platform/organizations | List organizations (admin) | org.read | none | |
GET /platform/organizations/current/governance-summary | Org-governance counts for the Organization Admin dashboard | org.read | none | |
GET /platform/organizations/{orgId} | Get an organization by id (admin) | org.read | none | |
PATCH /platform/organizations/{orgId} | Update an organization (admin) | org.manage | none | |
POST /platform/organizations/{orgId}/suspend | Suspend an organization (admin) | org.manage | none | |
POST /platform/organizations/{orgId}/restore | Restore an organization (admin) | org.manage | none |
Participants
Screening, eligibility, enrollment and status changes.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/subjects/summary | The counts a subjects page draws (by status, eligibility and site), under the reader's site scope, without the roster | subject.read | required | |
GET /studies/{studyId}/subjects | List a study's subjects (offset-paginated table page) | study.version.read | required | |
POST /studies/{studyId}/subjects | Screen (intake) a subject | subject.write (no administrator override) | required | enrol not allowed |
GET /studies/{studyId}/subjects/{subjectId} | Get a subject | clinical.event.read | required | |
GET /studies/{studyId}/subjects/{subjectId}/visits | List a subject's materialized visit instances | subject.read | required | |
GET /studies/{studyId}/subjects/{subjectId}/open-items | What is still open for a subject: queries, forms not yet signed, follow-up reports still due | subject.read | required | |
POST /studies/{studyId}/subjects/{subjectId}/eligibility | Determine a subject's eligibility (investigator attestation) | subject.eligibility (no administrator override) | required | data_entry not allowed |
POST /studies/{studyId}/subjects/{subjectId}/enroll | Enroll a subject (assigns subject number + baseline, materializes visits) | subject.write (no administrator override) | required | enrol not allowed |
POST /studies/{studyId}/subjects/{subjectId}/transition | Transition a subject (randomize / activate / screen_fail / withdraw / lost_to_follow_up / complete) | subject.write or subject.delete | required | data_entry not allowed |
POST /studies/{studyId}/subjects/{subjectId}/withdraw | Withdraw a subject (reason required) | subject.delete (no administrator override) | required | data_entry not allowed |
POST /studies/{studyId}/subjects/{subjectId}/safety-follow-up/complete | Complete the safety follow-up a withdrawal said was owed (the note says what was done) | clinical.event.report (no administrator override) | required | safety not allowed |
POST /studies/{studyId}/subjects/{subjectId}/unscheduled-visit | Create an unscheduled visit instance (events found at it are recorded as events, with the visit as where) | subject.write (no administrator override) | required | data_entry not allowed |
POST /studies/{studyId}/subjects/{subjectId}/visits/{visitInstanceId}/early-access | PI/CRC records an audited exception to enter data before a visit's window opens | subject.write (no administrator override) | required | data_entry not allowed |
POST /studies/{studyId}/subjects/{subjectId}/visits/{visitInstanceId}/mode | Say how a visit was held, on site or remotely (phone, video or at the subject's home), with the reason | subject.write (no administrator override) | required | data_entry not allowed |
GET /studies/{studyId}/subjects/{subjectId}/reconsents | What the subject answered to amendments that ask them to consent again, newest first, and what still asks them | subject.read | required | |
POST /studies/{studyId}/subjects/{subjectId}/reconsents | Record a subject’s consent, or refusal, when an amendment asks them to consent again | clinical.consent.write (no administrator override) | required | cleaning not allowed |
People
Members of the organization, invitations, suspension and removal.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /platform/users | List org users (admin) | user.directory.read | none | |
POST /platform/users | Invite a new org user (admin) | user.manage | none | |
GET /platform/users/{userId} | Get an org user (admin) | user.directory.read | none | |
PATCH /platform/users/{userId} | Update an org user (admin) | user.manage | none | |
POST /platform/users/{userId}/suspend | Suspend an org user (admin) | user.manage | none | |
POST /platform/users/{userId}/restore | Restore an org user (admin) | user.manage | none | |
POST /platform/users/{userId}/end-membership | Remove 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 organization | user.manage | none | |
POST /platform/users/{userId}/grant-org-admin | Grant Organization Admin to an existing org member (admin, self-service co-admin) | user.manage | none | |
POST /platform/users/invitations/bulk | Invite a team: up to 200 rows, each judged and sent on its own (admin) | user.manage | none | |
GET /platform/users/invitations | The organization's invitations with the status of each one's mail (admin) | user.manage | none | |
POST /platform/users/invitations/{invitationId}/resend | Queue a fresh mail for one pending invitation (admin) | user.manage | none | |
POST /platform/users/invitations/{invitationId}/revoke | Revoke an invitation that has not been answered (admin) | user.manage | none |
Protocol deviations
Reporting and reviewing deviations.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/deviations | List a study's protocol deviations (offset-paginated table page) | clinical.query.read | required | cleaning not allowed |
POST /studies/{studyId}/deviations | Report a protocol deviation | deviation.report (no administrator override) | required | cleaning not allowed |
PATCH /studies/{studyId}/deviations/{deviationId}/status | Update a protocol deviation review status | deviation.report (no administrator override) | required | cleaning not allowed |
Queries
Raising, answering, closing, reopening and escalating queries.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/queries | List a study's queries (offset-paginated table page) | clinical.query.read | required | |
POST /studies/{studyId}/queries | Raise a query | clinical.query.raise (no administrator override) | required | cleaning not allowed |
GET /studies/{studyId}/queries/stats | Query KPI counts for the study (open / overdue / auto / manual) | clinical.query.read | required | |
GET /studies/{studyId}/queries/{queryId} | Get a query with its thread | clinical.query.read | required | |
POST /studies/{studyId}/queries/{queryId}/answer | Answer a query (the site responds; not the person who raised it: 409 clinical.separation_of_duties) | clinical.query.answer (no administrator override) | required | cleaning not allowed |
POST /studies/{studyId}/queries/{queryId}/transition | Close / reopen / escalate a query (not a close by the person who answered it: 409 clinical.separation_of_duties) | depends on the action: see the page | required | cleaning not allowed |
Reports
Enrollment, sites, query aging, visit compliance, deviations, lock readiness.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/reports/enrollment | Enrollment report of one study | subject.read | required | |
GET /studies/{studyId}/reports/queries | Query-aging report of one study | clinical.query.read | required | |
GET /studies/{studyId}/reports/sites | Site-performance report of one study | site.read | required | |
GET /studies/{studyId}/reports/site-performance | Each site of one study against its target, with what is open and verified, and a risk | site.read | required | |
GET /studies/{studyId}/reports/lock-readiness | What stands between one study and database lock, by site | study.read | required | |
GET /studies/{studyId}/reports/deviations | Protocol-deviation report of one study | clinical.query.read | required | |
GET /reports/enrollment | Cross-study enrollment report | subject.read | named | |
GET /reports/queries | Cross-study query-aging report | clinical.query.read | named | |
GET /reports/site-performance | Each site against its target, with what is open and verified, and a risk | site.read | named | |
GET /reports/sites | Cross-study site-performance report | site.read | named | |
GET /reports/deviations | Cross-study protocol-deviation report | clinical.query.read | named | |
GET /reports/signatures | Cross-study e-signature manifest | audit.read | named |
Roles
The standard roles, custom roles, their revisions and risk assessment.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /platform/rbac/permissions | The permission catalog | any signed-in user | none | |
GET /platform/rbac/roles | The role catalog (system + custom), or with assignable=true what a picker offers | any signed-in user | none | |
POST /platform/rbac/roles | Create a custom role | any signed-in user | none | |
GET /platform/rbac/roles/{roleId} | Get a role by id | any signed-in user | none | |
PATCH /platform/rbac/roles/{roleId} | Update a custom role | any signed-in user | none | |
POST /platform/rbac/roles/assess | Dry-run the role guardrails (writes nothing) | any signed-in user | none | |
GET /platform/rbac/roles/{roleId}/revisions | A custom role's history, oldest first | any signed-in user | none | |
POST /platform/rbac/roles/{roleId}/suspend | Suspend a custom role | any signed-in user | none | |
POST /platform/rbac/roles/{roleId}/restore | Restore a custom role | any signed-in user | none | |
GET /platform/rbac/templates | The standard roles as the onboarding wizard's starting points, grouped by where a role holds its permissions | any signed-in user | none | |
POST /platform/rbac/roles/assess-set | Judge every role of a set together: the review screen (writes nothing) | any signed-in user | none | |
POST /platform/rbac/roles/bulk | Create a set of roles, all or none, each as revision 1 (the onboarding wizard's second step) | any signed-in user | none | |
POST /platform/rbac/ai/suggest-permissions | Suggest the permissions that fit a role (never applied) | any signed-in user | none | |
POST /platform/rbac/ai/explain-warning | Explain a warning the role guardrails raised, in words | any signed-in user | none |
Rules
Cross-form rules.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/rules | List a study's cross-form rules | workflow.design or study.write or form.read | none | version_work not allowed |
POST /studies/{studyId}/rules | Create a cross-form rule | workflow.design or study.write | none | version_work not allowed |
GET /studies/{studyId}/rules/{ruleId} | Get a rule | workflow.design or study.write or form.read | none | version_work not allowed |
PATCH /studies/{studyId}/rules/{ruleId} | Update a rule | workflow.design or study.write | none | version_work not allowed |
POST /studies/{studyId}/rules/{ruleId}/status | Activate / disable / retire a rule | workflow.design or study.write | none | version_work not allowed |
Safety events
Adverse events, serious events, medical review, reconciliation and follow-up.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/subjects/{subjectId}/events | List a subject's events (the removed ones only when asked for) | clinical.event.read | required | |
POST /studies/{studyId}/subjects/{subjectId}/events | Record an event (an adverse event, a concomitant medication); the form says what kind it is | clinical.event.write (no administrator override) | required | |
GET /studies/{studyId}/subjects/{subjectId}/events/{eventId} | Get an event | clinical.event.read | required | |
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/upgrade-to-sae | Upgrade an adverse event to a serious one: a new event linked to it, with the report and the version's reporting clock | clinical.event.write (no administrator override) | required | |
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/report | Set the safety-report status of a serious event (expedited reporting, ICH E2A); a report that was sent is final | clinical.event.report (no administrator override) | required | |
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/awareness | Correct when the site became aware of a serious event, with the reason; the due time is computed again | clinical.event.report (no administrator override) | required | |
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/status | Open or close an event (ongoing, or ended) | clinical.event.write (no administrator override) | required | |
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/remove | Remove 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-ups | List the follow-up reports of a serious event, by number (the removed ones only when asked for) | clinical.event.read | required | |
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/follow-ups | Record 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}/status | Set the status of a follow-up report (pending, sent, not required); a follow-up that was sent is final | clinical.event.report (no administrator override) | required | |
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/follow-ups/{followUpId}/remove | Remove a mistaken follow-up report, with the reason (it stays on record, with its number); a sent one cannot be removed | clinical.event.report (no administrator override) | required | |
GET /studies/{studyId}/subjects/{subjectId}/events/{eventId}/reviews | List 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.read | required | |
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/reviews | Record 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}/reconciliations | List 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.read | required | |
POST /studies/{studyId}/subjects/{subjectId}/events/{eventId}/reconciliations | Record 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-only | clinical.event.reconcile (no administrator override) | required |
Safety logs
The AE and SAE logs.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/safety/ae-log | The AE log: one entry per adverse event, in the session's workspace and at the reader's sites | clinical.event.read | required | |
GET /studies/{studyId}/safety/sae-log | The SAE log: one entry per serious adverse event, with its report, its clock and the adverse event it was upgraded from | clinical.event.read | required | |
GET /studies/{studyId}/safety/med-log | The concomitant medications: one entry per medication event, in the session's workspace and at the reader's sites | clinical.event.read | required | |
GET /studies/{studyId}/safety/other-events | The other events: one entry per event of an "other" event form, with the first few data fields of that form | clinical.event.read | required |
Signatures
The register of electronic signatures.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/signatures | List a study's applied signatures (the manifest) | audit.read | required | |
GET /studies/{studyId}/signatures/{signatureId} | Get an applied signature | audit.read | required |
Signing in and sessions
One-time codes, the choice of organization and workspace, tokens and your own sessions.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
POST /auth/otp/request | Request a one-time code — to sign in, or to sign | No sign-in needed to ask for a sign-in code; asking for a signing code needs a signed-in session | none | |
POST /auth/otp/verify | Verify an OTP — completes login, or asks which organization to sign in to | no sign-in needed | none | |
POST /auth/organisation/select | Choose the organization to sign in to (people who belong to several) | no sign-in needed | none | |
POST /auth/workspace | Enter a version workspace (UAT or Live), or leave it — a new session for the same sign-in | any signed-in user | none | |
POST /auth/refresh | Exchange a refresh token for a fresh token pair | no sign-in needed | none | |
POST /auth/logout | Revoke the current session | any signed-in user | none | |
GET /auth/me | The current user, active role, and available roles | any signed-in user | none | |
GET /auth/sessions | The caller's own active/recent sessions (device management) | any signed-in user | none | |
POST /auth/sessions/{sessionId}/revoke | Revoke one of the caller's OWN sessions (not the current one — use /auth/logout for that) | any signed-in user | none | |
POST /auth/invitation/open | What an invitation link says: the organization, and while it is valid the address to sign in with (no session; not consumed) | no sign-in needed | none | |
POST /auth/invitation/resend | A person whose link expired asks for a fresh one, by address (no session; answered the same whoever is invited) | no sign-in needed | none |
Sites
Registering, activating and closing sites; their staff and delegations.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/sites | List a study's sites | site.read | named | |
POST /studies/{studyId}/sites | Register a new site | site.write | required | governance not allowed |
GET /studies/{studyId}/sites/{siteId} | Get a site | site.read | required | |
POST /studies/{studyId}/sites/{siteId}/activate | Activate a site (IRB approval + training gate) | site.activate | required | governance not allowed |
PATCH /studies/{studyId}/sites/{siteId}/target | Set a site's enrollment target (or clear it with null) | site.write | required | governance not allowed |
POST /studies/{studyId}/sites/{siteId}/transition | Suspend / resume / close a site | site.transition | required | governance not allowed |
Source data verification
The SDV plan and the monitor's queue.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/sdv/plan | List the active SDV targeting plan | any signed-in user | required | |
POST /studies/{studyId}/sdv/plan | Add or update a SDV requirement (risk-based targeting) | any signed-in user | required | cleaning not allowed |
DELETE /studies/{studyId}/sdv/plan/{itemId} | Retire a SDV requirement (soft-delete) | any signed-in user | required | cleaning not allowed |
GET /studies/{studyId}/sdv/subjects | Every subject SDV status in one read (monitoring dashboard) | any signed-in user | required | |
GET /studies/{studyId}/sdv/subjects/{subjectId} | Read a subject's SDV plan + live datapoint verification status | any signed-in user | required | |
POST /studies/{studyId}/sdv/subjects/{subjectId}/verify | Record a source-data-verification action on a datapoint | any signed-in user | required | cleaning not allowed |
Studies
Creating a study, its state, lock and archive.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
PATCH /studies/{studyId} | Update persisted study metadata | study.write | none | metadata not allowed |
GET /studies/{studyId} | Get a study by id | any signed-in user | none | |
GET /studies/aggregates/compliance | Read portfolio visit-compliance report (Reports > Visit Compliance) | study.read | named | |
GET /studies/{studyId}/reports/compliance | Read the visit-compliance report of one study | site.read | required | |
GET /studies | List studies (portfolio) | study.read | none | |
POST /studies | Create a study (admin / PM) | study.create | none | |
GET /studies/{studyId}/summary | Cross-module aggregate metrics (enrollment, queries, visit compliance, sites) | any signed-in user | required | |
GET /studies/{studyId}/state | The study's operational state, the actions the caller may take, the lock request waiting for a second person, and the pre-lock listing | any signed-in user | none | |
POST /studies/{studyId}/state | Change 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 user | none | |
POST /studies/{studyId}/state/request/cancel | Cancel 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.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/team | List a study's role assignments | user.manage or study.write or site.read | named | governance not allowed |
POST /studies/{studyId}/team | Provision a role assignment (site staff stay pending PI delegation) | team.assign | named | governance not allowed |
GET /studies/{studyId}/team/sites | List a study's sites in UAT and in Live, for the team's site tabs | user.manage or study.write or site.read | named | governance not allowed |
GET /studies/{studyId}/team/{assignmentId} | Get a role assignment | user.manage or study.write or site.read | named | governance not allowed |
POST /studies/{studyId}/team/{assignmentId}/delegate | PI signs the delegation-log entry waiting for them — e-signed (21 CFR 11.50) | delegation.sign (no administrator override) | named | governance not allowed |
GET /studies/{studyId}/team/{assignmentId}/delegations | A site member's delegation log: every entry, including those that ended, with the names of who proposed, signed and ended each | user.manage or study.write or site.read | named | governance not allowed |
POST /studies/{studyId}/team/{assignmentId}/delegations | Propose an amendment of a site member's delegated tasks and dates, for the PI to sign | team.assign or delegation.sign | named | governance not allowed |
GET /studies/{studyId}/team/{assignmentId}/delegable-tasks | The tasks a delegation of this site member can name: those their role covers | team.assign or delegation.sign | named | governance not allowed |
POST /studies/{studyId}/team/{assignmentId}/delegations/{delegationId}/end | Withdraw a proposed delegation entry, or end a signed one before its end date | team.assign or delegation.sign | named | governance not allowed |
POST /studies/{studyId}/team/{assignmentId}/manage | Suspend / resume / retire / complete-training an assignment | team.training_complete or team.manage | named | governance not allowed |
Versions
The design's life: build, UAT cycles, sign-offs, go live, amendments and re-consent.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/versions | List a study's versions, newest first | study.version.read | none | version_work not allowed |
POST /studies/{studyId}/versions | Start an amendment (the next version of a Live study) | study.amendment.start | none | version_work not allowed |
GET /studies/{studyId}/versions/{versionId} | Get a version with its cycles, sign-offs and amendment | study.version.read | none | version_work not allowed |
PATCH /studies/{studyId}/versions/{versionId} | Edit the summary and settings of a version in Build | study.write | none | version_work not allowed |
GET /studies/{studyId}/versions/{versionId}/snapshots/{snapshotId} | The compiled snapshot a UAT cycle tested, with its hashes | study.version.read | none | version_work not allowed |
GET /studies/{studyId}/versions/{versionId}/cycles | The UAT cycles of a version, with the test data each holds | study.version.read | named | version_work not allowed |
GET /studies/{studyId}/versions/{versionId}/cycles/{number} | One UAT cycle of a version by its number | study.version.read | named | version_work not allowed |
GET /studies/{studyId}/versions/{versionId}/revisions | The draft history of a version, newest first, a page at a time | study.version.read | none | version_work not allowed |
GET /studies/{studyId}/versions/{versionId}/build | What a version’s Build stands at | study.version.read | none | version_work not allowed |
GET /studies/{studyId}/versions/{versionId}/go-live | What stands between a version and Live | study.version.read | none | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/go-live | Take an approved version Live | study.version.go_live | none | version_work not allowed |
GET /studies/{studyId}/versions/{versionId}/changes | What an amendment changed against the Live version it started from | study.version.read | none | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/submit-to-uat | Submit a version in Build to UAT: publish its forms, compile the snapshot, open a cycle | study.version.submit_uat | none | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/pass | Pass the open UAT cycle | study.uat.close_cycle | named | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/fail | Fail the open UAT cycle (back to Build) | study.uat.close_cycle | named | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/signoff | Sign off (or reject) a version that passed UAT; the label is derived from the signer’s role | study.version.signoff (no administrator override) | none | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/withdraw | Withdraw a version that is not yet Live (reason required) | study.version.withdraw | none | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/transition-subjects | Move 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, retired | study.version.go_live | named | version_work not allowed |
GET /studies/{studyId}/versions/{versionId}/cycles/{number}/issues | The issues of one UAT cycle, newest first, with their counts | study.uat_issue.read | named | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/issues | Raise an issue in the open UAT cycle | study.uat_issue.write | named | version_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 cycle | study.uat_issue.write | named | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/issues/{issueNumber}/resolve | Resolve an issue: settled (not a defect, cleared, or answered); a note may say how | study.uat_issue.write | named | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/issues/{issueNumber}/accept | Accept an issue: a known limitation taken knowingly; the reason is required | study.uat_issue.write | named | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/issues/{issueNumber}/reopen | Reopen a resolved or accepted issue | study.uat_issue.write | named | version_work not allowed |
GET /studies/{studyId}/versions/{versionId}/rounds | The rounds of a version's Build: what each was asked, what its saves did, how it ended | study.uat_issue.read | named | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/corrections/{issueNumber}/resolve | Mark a finding of the latest failed UAT cycle resolved in the design; a note may say what was done | form.design | named | version_work not allowed |
POST /studies/{studyId}/versions/{versionId}/cycles/{number}/corrections/{issueNumber}/reopen | Take back a finding marked resolved | form.design | named | version_work not allowed |
GET /studies/{studyId}/versions/{versionId}/reconsent | Where the study’s Live subjects stand against an amendment that asks them to consent again | study.version.read | required | |
GET /studies/{studyId}/versions/{versionId}/affected-subjects | Which Live subjects an amendment will move when it goes Live, and which will wait for a re-consent | study.version.read | named |
Visit and form mapping
Which forms are collected at which visits.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/mapping | List the form↔visit mapping matrix | form.read or visit.read or form.design or study.write | none | version_work not allowed |
POST /studies/{studyId}/mapping | Bind 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.write | none | version_work not allowed |
GET /studies/{studyId}/mapping/{mappingId} | Get a mapping cell | form.read or visit.read or form.design or study.write | none | version_work not allowed |
PATCH /studies/{studyId}/mapping/{mappingId} | Update a mapping cell (required/condition/order/state) | form.design or study.write | none | version_work not allowed |
DELETE /studies/{studyId}/mapping/{mappingId} | Unbind a form from a visit | form.design or study.write | none | version_work not allowed |
Visit schedule
Protocol visits and windows.
| Operation | What it does | Who may call | Workspace | Refused when |
|---|---|---|---|---|
GET /studies/{studyId}/visits | List a study's visit schedule | visit.read | none | version_work not allowed |
POST /studies/{studyId}/visits | Add a visit to the schedule | form.design or study.write | none | version_work not allowed |
GET /studies/{studyId}/visits/{visitId} | Get a visit | visit.read | none | version_work not allowed |
PATCH /studies/{studyId}/visits/{visitId} | Update a visit | form.design or study.write | none | version_work not allowed |
POST /studies/{studyId}/visits/{visitId}/status | Activate or archive a visit | form.design or study.write | none | version_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.
| Operation | What it does | Who may call |
|---|---|---|
GET /api/platform/admins | Lists the Super-Admins with their state (invited, active, suspended) | a signed-in Super-Admin |
POST /api/platform/admins | Invites a Super-Admin and mails the invitation | a signed-in Super-Admin |
POST /api/platform/admins/:adminId/suspend | Suspends a Super-Admin and ends their sessions (not yourself, not the last active one) | a signed-in Super-Admin |
POST /api/platform/admins/:adminId/restore | Restores a suspended Super-Admin | a signed-in Super-Admin |
GET /api/platform/audit | Reads the platform ledger, 50 to 200 at a time, newest first, filtered by action prefix or target | a signed-in Super-Admin |
GET /api/platform/organizations | Lists the organizations | a signed-in Super-Admin |
PATCH /api/platform/organizations/:orgId | Corrects an organization's name, code, type, country or first administrator, with a reason | a signed-in Super-Admin |
POST /api/platform/organizations | Provisions an organization and pushes it to the data plane, with its first Organization Admin | a signed-in Super-Admin |
POST /api/platform/organizations/:orgId/sync | Pushes an organization to the data plane again (idempotent) | a signed-in Super-Admin |
GET /api/platform/organizations/:orgId | Reads one organization | a signed-in Super-Admin |
GET /api/platform/organizations/:orgId/admin-invitations | Reads the first administrators' invitations from the data plane | a signed-in Super-Admin |
POST /api/platform/organizations/:orgId/admin-invitations/resend | Sends the pending first-administrator invitation again | a signed-in Super-Admin |
POST /api/platform/organizations/:orgId/suspend | Suspends an organization (a reason of at least five characters); the data plane is told first | a signed-in Super-Admin |
POST /api/platform/organizations/:orgId/restore | Restores a suspended organization (the reason is optional) | a signed-in Super-Admin |
POST /api/platform/organizations/:orgId/close | Closes an organization for good (a reason); the data plane treats it as suspended | a signed-in Super-Admin |
GET /api/platform/organizations/:orgId/usage | Reads live counts from the data plane (users, studies, exports, stored bytes) | a signed-in Super-Admin |
GET /api/platform/organizations/:orgId/entitlements | Lists the five feature switches of an organization | a signed-in Super-Admin, or the data plane with its narrow key |
PUT /api/platform/organizations/:orgId/entitlements/:featureKey | Switches one feature on or off (not sso or api.access) | a signed-in Super-Admin |
GET /api/platform/stats | Counts organizations by status | a signed-in Super-Admin |
GET /api/platform/stats/data-plane | Asks the data plane about every pushed organization: invitations and usage | a signed-in Super-Admin |
POST /api/platform/auth/otp/request | Asks for a six-digit sign-in code (answers the same for any address) | no sign-in needed |
POST /api/platform/auth/otp/verify | Exchanges the code for a session | no sign-in needed |
POST /api/platform/auth/refresh | Renews a session from its refresh token | no sign-in needed (the refresh token) |
POST /api/platform/auth/logout | Ends the caller's own session | a signed-in Super-Admin |
GET /api/platform/auth/me | Who the caller is | a signed-in Super-Admin |
Compliance Q&A
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
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.
Related
Roles and permissions · Error messages · Audit events · API and integrations · Architecture