All endpoints are prefixed with /api/v1
All API requests and responses include a correlation ID for request tracking:
- Request Header:
X-Correlation-ID(optional) — if provided, this ID will be used; otherwise, a new UUID will be generated - Response Header:
X-Correlation-ID— the correlation ID for this request - Error Responses: All error responses include
correlation_idin the response body
The correlation ID is also included in all log messages, allowing you to trace a request through the system by searching logs for the correlation ID.
Method: GET
Path: /health
Response Body:
{
"status": "ok"
}Method: POST
Path: /api/v1/auth/register
Request Body:
{
"username": "string",
"full_name": "string | null",
"email": "string | null",
"password": "string"
}Response Body:
{
"id": 0,
"username": "string",
"full_name": "string | null",
"email": "string | null",
"is_active": true
}Error Responses:
400: Username (or email) already registered
Response Headers:
X-Correlation-ID: Correlation ID for this request
Method: POST
Path: /api/v1/auth/token
Request Body:
- Content-Type:
application/x-www-form-urlencoded username(string, required)password(string, required)
Response Body:
{
"access_token": "string",
"token_type": "bearer"
}Error Responses:
401: Incorrect username or password403: User inactive
Method: POST
Path: /api/v1/auth/logout
Response Body:
{
"detail": "logged out"
}Notes:
- JWT tokens are stateless, so this endpoint currently just returns a success message
- The client should discard the token on their side
- Future implementation may include token blacklist/revocation mechanism
Authorization: Requires
Authorization: Bearer <token>header obtained from the Auth endpoints.
Method: POST
Path: /api/v1/studies
Request Body:
{
"code": "string",
"title": "string",
"phase": "string | null",
"status": "string",
"indication": "string | null",
"sponsor_name": "string | null"
}Response Body:
{
"id": 0,
"code": "string",
"title": "string",
"phase": "string | null",
"status": "string",
"indication": "string | null",
"sponsor_name": "string | null"
}Notes:
statusdefaults to"draft"if not provided- Valid status values:
"draft","ongoing","closed","archived" - The creator is automatically added as a study member with role
"owner"
Error Responses:
400: Study with this code already exists401/403: Missing or invalid token
Method: GET
Path: /api/v1/studies
Response Body:
[
{
"id": 0,
"code": "string",
"title": "string",
"phase": "string | null",
"status": "string",
"indication": "string | null",
"sponsor_name": "string | null"
}
]Notes:
- Returns only studies where the current user is a member
Error Responses:
401/403: Missing or invalid token
Method: GET
Path: /api/v1/studies/{study_id}
Path Parameters:
study_id(integer, required): The ID of the study
Response Body:
{
"id": 0,
"code": "string",
"title": "string",
"phase": "string | null",
"status": "string",
"indication": "string | null",
"sponsor_name": "string | null"
}Notes:
- Only members of the study can view it
- Returns 404 if study doesn't exist
- Returns 403 if user is not a member of the study
Error Responses:
403: Access denied (user is not a member of the study)404: Study not found
Method: GET
Path: /api/v1/studies/{study_id}/members
Path Parameters:
study_id(integer, required): The ID of the study
Response Body:
[
{
"id": 0,
"study_id": 0,
"user_id": 0,
"role": "string",
"created_at": "2024-01-01T00:00:00Z",
"user": {
"id": 0,
"username": "string",
"full_name": "string | null",
"email": "string | null"
}
}
]Notes:
- Only study members can view the list of members
- Roles:
"owner","editor","viewer"
Error Responses:
403: Access denied (user is not a member of the study)404: Study not found
Method: GET
Path: /api/v1/studies/{study_id}/members/me
Path Parameters:
study_id(integer, required): The ID of the study
Response Body:
{
"id": 0,
"study_id": 0,
"user_id": 0,
"role": "string",
"created_at": "2024-01-01T00:00:00Z",
"user": {
"id": 0,
"username": "string",
"full_name": "string | null",
"email": "string | null"
}
}Notes:
- Returns the current user's membership information in the specified study
- Role can be:
"owner","editor", or"viewer"
Error Responses:
403: Access denied (user is not a member of this study)404: Study not found
Method: POST
Path: /api/v1/studies/{study_id}/members/{user_id}
Path Parameters:
study_id(integer, required): The ID of the studyuser_id(integer, required): The ID of the user to add
Request Body:
{
"role": "string"
}Response Body:
{
"id": 0,
"study_id": 0,
"user_id": 0,
"role": "string",
"created_at": "2024-01-01T00:00:00Z",
"user": {
"id": 0,
"username": "string",
"full_name": "string | null",
"email": "string | null"
}
}Notes:
- Only study owners can add members
- Valid roles:
"owner","editor","viewer" - Cannot add the same user twice
- Cannot change study owner through this endpoint
Error Responses:
400: User already in study, or cannot change study owner403: Access denied (only owners can add members)404: Study or user not found
Method: DELETE
Path: /api/v1/studies/{study_id}/members/{member_id}
Path Parameters:
study_id(integer, required): The ID of the studymember_id(integer, required): The user ID of the study member to remove (note: this is the user_id, not the StudyMember record ID)
Response Body:
- No content (204 No Content)
Notes:
- Only study owners can remove members
- Cannot remove the last owner of the study
- The
member_idparameter is actually theuser_idof the member to remove
Error Responses:
400: Cannot remove the last owner of the study403: Access denied (only owners can remove members)404: Member not found
Authorization: Requires
Authorization: Bearer <token>header. User must be a member of the study.
⚠️ DEPRECATED: These endpoints are deprecated. Use the/api/v1/output/...endpoints instead. The CSR endpoints will be removed in a future major version but currently behave identically to the OutputDocument endpoints.
Method: GET
Path: /api/v1/csr/{study_id}
Path Parameters:
study_id(integer, required): The ID of the study
Response Body:
{
"id": 0,
"study_id": 0,
"title": "string",
"status": "string",
"sections": [
{
"id": 0,
"code": "string",
"title": "string",
"order_index": 0
}
]
}Notes:
- If the CSR document doesn't exist for the study, it will be automatically created with default sections.
- This endpoint uses the same normalized CSR loading logic as
GET /api/v1/csr/document/{document_id}, ensuring consistent behavior. - Internally, it finds or creates a default
Documentof type "csr" for the study, then ensures the linkedOutputDocumentexists. - The returned CSR document may have a
document_idfield linking it to theDocumentmodel for navigation consistency.
Error Responses:
404: Study not found
Method: GET
Path: /api/v1/csr/document/{document_id}
Path Parameters:
document_id(integer, required): The ID of the Document (must be type "csr")
Response Body:
{
"id": 0,
"study_id": 0,
"title": "string",
"status": "string",
"sections": [
{
"id": 0,
"code": "string",
"title": "string",
"order_index": 0
}
]
}Notes:
- Loads the Document by ID, ensures user has access to its study, and checks that
document.type == "csr" - Returns the linked OutputDocument structure for editing
- If the OutputDocument doesn't exist for the Document, it will be automatically created with default sections
- The frontend can navigate by
document_idinstead of only bystudy_id - If an OutputDocument already exists for the study but is not linked to the Document, it will be linked automatically
- This endpoint uses the same normalized CSR loading logic as
GET /api/v1/csr/{study_id}, ensuring consistent behavior - The returned
document_idcan be used consistently across CSR, QC, and RAG endpoints
Error Responses:
400: Document type is not "csr"403: User is not a member of the study404: Document or study not found
Method: GET
Path: /api/v1/csr/{study_id}/sections
Path Parameters:
study_id(integer, required): The ID of the study
Response Body:
[
{
"id": 0,
"code": "string",
"title": "string",
"order_index": 0
}
]Notes:
- Returns all sections for the CSR document of the study, ordered by
order_index. - If the CSR document doesn't exist for the study, it will be automatically created with default sections.
Error Responses:
404: Study not found
Method: GET
Path: /api/v1/csr/sections/{section_id}/versions/latest
Path Parameters:
section_id(integer, required): The ID of the CSR section
Response Body:
{
"id": 0,
"text": "string",
"created_at": "2024-01-01T00:00:00Z",
"created_by": "string | null",
"source": "string | null"
}Error Responses:
404: Section not found404: No versions found for this section
Method: POST
Path: /api/v1/csr/sections/{section_id}/versions
Path Parameters:
section_id(integer, required): The ID of the CSR section
Request Body:
{
"text": "string",
"created_by": "string | null"
}Response Body:
{
"id": 0,
"text": "string",
"created_at": "2024-01-01T00:00:00Z",
"created_by": "string | null",
"source": "string | null"
}Notes:
- Creates a new version with
source="human". - Returns status code
201 Created.
Error Responses:
404: Section not found
Method: POST
Path: /api/v1/csr/sections/{section_id}/apply-template
Path Parameters:
section_id(integer, required): The ID of the CSR section
Request Body:
{
"template_id": 0,
"study_id": 0,
"extra_context": {}
}Response Body:
{
"id": 0,
"text": "string",
"created_at": "2024-01-01T00:00:00Z",
"created_by": "string | null",
"source": "template",
"template_id": 0
}Notes:
- Requires authentication
- Creates a new section version with rendered template content
- Template is rendered with context from study data and RAG chunks
- Returns status code
201 Created - The response includes
template_idfield linking to the template used
Error Responses:
400: Section does not belong to the specified study401/403: Missing or invalid token, or user is not a member of the study404: Section, study, or template not found
Method: GET
Path: /api/v1/csr/{study_id}/export/docx
Path Parameters:
study_id(integer, required): The ID of the study
Response:
- Content-Type:
application/vnd.openxmlformats-officedocument.wordprocessingml.document - Content-Disposition:
attachment; filename="csr_{study_code}.docx" - Body: Binary DOCX file content
Notes:
- Requires authentication (user must be a member of the study)
- If the CSR document doesn't exist, it will be automatically created with default sections
- Exports the CSR document with all sections and their latest versions
- Document structure:
- Document title as main heading (centered)
- Section titles as level 1 headings
- Section text as paragraphs
- Filename is generated as
csr_{study_code}.docxorcsr_{study_id}.docxif code is not available
Error Responses:
401/403: Missing or invalid token, or user is not a member of the study404: Study not found, or CSR document not found (after creation attempt)
Authorization: Requires
Authorization: Bearer <token>header. User must be a member of the study.
The OutputDocument endpoints expose the OutputDocument domain, which represents structured editable documents (currently used for CSR/Clinical Study Reports). These endpoints replace the deprecated /api/v1/csr/... endpoints and use OutputDocument terminology.
Method: GET
Path: /api/v1/output/{study_id}
Path Parameters:
study_id(integer, required): The ID of the study
Response Body:
{
"id": 0,
"study_id": 0,
"title": "string",
"status": "string",
"sections": [
{
"id": 0,
"code": "string",
"title": "string",
"order_index": 0
}
]
}Notes:
- If the Output Document doesn't exist for the study, it will be automatically created with default sections.
- This endpoint uses the same normalized OutputDocument loading logic as
GET /api/v1/output/document/{document_id}, ensuring consistent behavior. - Internally, it finds or creates a default
Documentof type "csr" for the study, then ensures the linkedOutputDocumentexists. - The returned Output Document may have a
document_idfield linking it to theDocumentmodel for navigation consistency.
Error Responses:
404: Study not found
Method: GET
Path: /api/v1/output/document/{document_id}
Path Parameters:
document_id(integer, required): The ID of the Document (must be type "csr")
Response Body:
{
"id": 0,
"study_id": 0,
"title": "string",
"status": "string",
"sections": [
{
"id": 0,
"code": "string",
"title": "string",
"order_index": 0
}
]
}Notes:
- Loads the Document by ID, ensures user has access to its study, and checks that
document.type == "csr" - Returns the linked OutputDocument structure for editing
- If the OutputDocument doesn't exist for the Document, it will be automatically created with default sections
- The frontend can navigate by
document_idinstead of only bystudy_id - If an OutputDocument already exists for the study but is not linked to the Document, it will be linked automatically
- This endpoint uses the same normalized OutputDocument loading logic as
GET /api/v1/output/{study_id}, ensuring consistent behavior - The returned
document_idcan be used consistently across OutputDocument, QC, and RAG endpoints
Error Responses:
400: Document type is not "csr"403: User is not a member of the study404: Document or study not found
Method: GET
Path: /api/v1/output/{study_id}/sections
Path Parameters:
study_id(integer, required): The ID of the study
Response Body:
[
{
"id": 0,
"code": "string",
"title": "string",
"order_index": 0
}
]Notes:
- Returns all sections for the Output Document of the study, ordered by
order_index. - If the Output Document doesn't exist for the study, it will be automatically created with default sections.
Error Responses:
404: Study not found
Method: GET
Path: /api/v1/output/sections/{section_id}/versions/latest
Path Parameters:
section_id(integer, required): The ID of the Output Document section
Response Body:
{
"id": 0,
"text": "string",
"created_at": "2024-01-01T00:00:00Z",
"created_by": "string | null",
"source": "string | null"
}Error Responses:
404: Section not found404: No versions found for this section
Method: POST
Path: /api/v1/output/sections/{section_id}/versions
Path Parameters:
section_id(integer, required): The ID of the Output Document section
Request Body:
{
"text": "string",
"created_by": "string | null"
}Response Body:
{
"id": 0,
"text": "string",
"created_at": "2024-01-01T00:00:00Z",
"created_by": "string | null",
"source": "string | null"
}Notes:
- Creates a new version with
source="human". - Returns status code
201 Created.
Error Responses:
404: Section not found
Method: POST
Path: /api/v1/output/sections/{section_id}/apply-template
Path Parameters:
section_id(integer, required): The ID of the Output Document section
Request Body:
{
"template_id": 0,
"study_id": 0,
"extra_context": {}
}Response Body:
{
"id": 0,
"text": "string",
"created_at": "2024-01-01T00:00:00Z",
"created_by": "string | null",
"source": "template",
"template_id": 0
}Notes:
- Requires authentication
- Creates a new section version with rendered template content
- Template is rendered with context from study data and RAG chunks
- Returns status code
201 Created - The response includes
template_idfield linking to the template used
Error Responses:
400: Section does not belong to the specified study401/403: Missing or invalid token, or user is not a member of the study404: Section, study, or template not found
Method: GET
Path: /api/v1/output/{study_id}/export/docx
Path Parameters:
study_id(integer, required): The ID of the study
Response:
- Content-Type:
application/vnd.openxmlformats-officedocument.wordprocessingml.document - Content-Disposition:
attachment; filename="csr_{study_code}.docx" - Body: Binary DOCX file content
Notes:
- Requires authentication (user must be a member of the study)
- If the Output Document doesn't exist, it will be automatically created with default sections
- Exports the Output Document with all sections and their latest versions
- Document structure:
- Document title as main heading (centered)
- Section titles as level 1 headings
- Section text as paragraphs
- Filename is generated as
csr_{study_code}.docxorcsr_{study_id}.docxif code is not available
Error Responses:
401/403: Missing or invalid token, or user is not a member of the study404: Study not found, or Output Document not found (after creation attempt)
Authorization: Requires
Authorization: Bearer <token>header. User must be a member of the study.
Method: POST
Path: /api/v1/ai/generate-section-text
Request Body:
{
"study_id": 0,
"section_id": 0,
"prompt": "string | null",
"max_tokens": 1024,
"temperature": 0.2
}Response Body:
{
"study_id": 0,
"section_id": 0,
"generated_text": "string",
"model_name": "string | null",
"created_at": "2024-01-01T00:00:00Z"
}Notes:
- Automatically retrieves RAG context from source documents (protocol, SAP, TLF, previous CSR)
- Uses prompt templates if available for the section, otherwise builds prompt using
ai_prompt_builder - Includes current section text (if exists) in the prompt for context-aware generation
- Supports two AI modes controlled by
AI_MODEconfiguration:"stub"(default): Returns placeholder text for development/testing"real": Calls external LLM API via HTTP (requiresAI_ENDPOINTand optionallyAI_API_KEYenvironment variables)
- All AI calls are logged in the database with the mode used (
modefield) - The effective prompt (after template rendering or prompt building) is logged in
ai_call_logs
Error Responses:
400: Section does not belong to study401/403: Missing or invalid token, or user is not a member of the study404: Section or study not found500: AI generation failed (includes error details inai_call_logswithsuccess=false)
Authorization: Requires
Authorization: Bearer <token>header. User must be a member of the study.
Method: POST
Path: /api/v1/sources/{study_id}/upload
Path Parameters:
study_id(integer, required): The ID of the study
Request Body:
- Content-Type:
multipart/form-data file(file, required): The document file to uploadtype(string, required): The type/category of the source document (e.g., "protocol", "sap", "tlf", "csr_prev")language(string, optional): Language code ("ru" or "en"), default "ru"version_label(string, optional): Version label for the document
Response Body:
{
"id": 0,
"study_id": 0,
"type": "string",
"file_name": "string",
"uploaded_at": "2024-01-01T00:00:00Z",
"uploaded_by": "string | null",
"language": "string",
"version_label": "string | null",
"status": "string",
"is_current": true,
"is_rag_enabled": true,
"index_status": "string"
}Notes:
- When uploading a new document with the same
typeandlanguage, previous documents are automatically marked asis_current=false - The uploaded document is automatically indexed for RAG if
is_rag_enabled=true(default) index_statusis set to "not_indexed" initially, then updated to "indexed" after successful RAG ingestion- RAG ingestion runs as a background task, so the response may return before indexing is complete
- The document is immediately available after upload, but RAG chunks may not be available until indexing completes
Error Responses:
400: File must have a filename, or language must be 'ru' or 'en'404: Study not found500: Failed to save file
Method: GET
Path: /api/v1/sources/{study_id}
Path Parameters:
study_id(integer, required): The ID of the study
Response Body:
[
{
"id": 0,
"study_id": 0,
"type": "string",
"file_name": "string",
"uploaded_at": "2024-01-01T00:00:00Z",
"uploaded_by": "string | null",
"language": "string",
"version_label": "string | null",
"status": "string",
"is_current": true,
"is_rag_enabled": true,
"index_status": "string"
}
]Notes:
- Documents are ordered by
uploaded_atin descending order (most recent first) - After upload, the document is automatically indexed for RAG (text extraction and chunking) if
is_rag_enabled=true index_statusindicates the indexing state: "not_indexed", "indexed", or "error"
Error Responses:
401/403: Missing or invalid token, or user is not a member of the study404: Study not found
Method: DELETE
Path: /api/v1/sources/{source_document_id}
Path Parameters:
source_document_id(integer, required): The ID of the source document to delete
Response Body:
- No content (204 No Content)
Notes:
- Performs a soft delete of the source document
- Sets
status="archived",is_current=false,is_rag_enabled=false, andindex_status="not_indexed" - Deletes all associated RAG chunks for the document
- Only study owners and editors can delete documents (viewers are not allowed)
- The file is not physically deleted from storage
- The document record remains in the database with archived status
Error Responses:
403: Access denied (user is not a member of the study, or user is a viewer)404: Source document not found
Method: DELETE
Path: /api/v1/sources/{source_document_id}/permanent
Path Parameters:
source_document_id(integer, required): The ID of the source document to permanently delete
Response Body:
- No content (204 No Content)
Notes:
- Permanently deletes a source document, its RAG chunks and stored file
- Only study owners can perform permanent delete (editors and viewers are not allowed)
- This action is irreversible:
- Deletes all associated RAG chunks from the database
- Physically deletes the file from storage
- Removes the SourceDocument record from the database
- If file deletion fails, the database record is still deleted (error is logged)
Error Responses:
403: Access denied (user is not a member of the study, or user is not an owner)404: Source document not found
Method: POST
Path: /api/v1/sources/{source_document_id}/restore
Path Parameters:
source_document_id(integer, required): The ID of the source document to restore
Response Body:
{
"id": 0,
"study_id": 0,
"type": "string",
"file_name": "string",
"uploaded_at": "2024-01-01T00:00:00Z",
"uploaded_by": "string | null",
"language": "string",
"version_label": "string | null",
"status": "active",
"is_current": false,
"is_rag_enabled": true,
"index_status": "not_indexed"
}Notes:
- Restores a previously archived source document back to active state
- Sets
status="active",is_rag_enabled=true,index_status="not_indexed" is_currentis set tofalseby default, ortrueif there are no other active documents of the same (study_id, type, language)- Automatically triggers RAG ingestion to re-index the document
- Only study owners and editors can restore documents (viewers are not allowed)
- The document will become usable for RAG queries again once re-indexed
Error Responses:
400: Document is not archived403: Access denied (user is not a member of the study, or user is a viewer)404: Source document not found
Authorization: Requires
Authorization: Bearer <token>header. User must be a member of the study.
Method: GET
Path: /api/v1/studies/{study_id}/documents
Path Parameters:
study_id(integer, required): The ID of the study
Query Parameters:
ensure_default_csr(boolean, optional): Iftrue, automatically creates a default CSR document if none exists for the study. Default:false
Response Body:
[
{
"id": 0,
"study_id": 0,
"type": "string",
"title": "string",
"template_code": "string | null",
"status": "string",
"current_version_label": "string | null",
"created_by": 0,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
]Notes:
- Returns all documents for the study, ordered by
typefirst, then bycreated_atin ascending order - Only study members can view documents
- Valid document types:
"csr","protocol","ib","sap","tlf" - Valid document statuses:
"draft","in_qc","ready_for_submission","archived" - If
ensure_default_csr=trueand no CSR document exists, a default CSR document is created with:title= "CSR for {study.code}" (or "CSR for Study {study_id}" if code is not available)status= "draft"created_by= current user
Error Responses:
401/403: Missing or invalid token, or user is not a member of the study404: Study not found
Method: POST
Path: /api/v1/studies/{study_id}/documents
Path Parameters:
study_id(integer, required): The ID of the study
Request Body:
{
"type": "string",
"title": "string",
"template_code": "string | null",
"status": "string"
}Response Body:
{
"id": 0,
"study_id": 0,
"type": "string",
"title": "string",
"template_code": "string | null",
"status": "string",
"current_version_label": "string | null",
"created_by": 0,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}Notes:
statusdefaults to"draft"if not provided- Valid document types:
"csr","protocol","ib","sap","tlf" - Valid status values:
"draft","in_qc","ready_for_submission","archived" - Only study owners and editors can create documents (viewers are not allowed)
- The
created_byfield is automatically set to the current user's ID - Returns status code
201 Created
Error Responses:
400: Invalid request data (e.g., invalidtypeorstatusvalue)401/403: Missing or invalid token, or user is not a member of the study, or user is a viewer404: Study not found
Authorization: Not required at this time.
Method: GET
Path: /api/v1/templates/section/{section_code}
Path Parameters:
section_code(string, required): The code of the CSR section (e.g.,SYNOPSIS,OBJECTIVES)
Query Parameters:
language(string, optional): Filter by language (e.g.,ru,en)scope(string, optional): Filter by scope (e.g.,global,sponsor,study)
Response Body:
[
{
"id": 0,
"name": "string",
"description": "string | null",
"type": "section_text",
"section_code": "string",
"language": "string",
"scope": "string",
"is_default": false,
"is_active": true,
"version": 0,
"content": "string",
"variables": {},
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z",
"created_by": "string | null"
}
]Notes:
- Returns only active templates of type
"section_text" - Templates are filtered by section code and optional language/scope
Method: POST
Path: /api/v1/templates/{template_id}/render
Path Parameters:
template_id(integer, required): The ID of the template
Request Body:
{
"study_id": 0,
"section_id": 0,
"extra_context": {}
}Response Body:
{
"rendered_text": "string",
"used_variables": {},
"missing_variables": []
}Notes:
- Renders template with context from study data and RAG chunks
- Variables in template use
{{variable_name}}syntax - Missing variables remain as placeholders in the rendered text
Error Responses:
404: Template or study not found
Authorization: Requires
Authorization: Bearer <token>header for getting chunks. User must be a member of the study.
Method: POST
Path: /api/v1/rag/ingest/{source_document_id}
Path Parameters:
source_document_id(integer, required): The ID of the source document to index
Response Body:
{
"source_document_id": 0,
"chunks_created": 0
}Notes:
- Extracts text from the source document
- Splits text into chunks and stores them in the RAG system
- Replaces existing chunks for the document if re-indexing
- This is automatically called after document upload
Error Responses:
404: Source document not found500: RAG ingestion failed
Method: GET
Path: /api/v1/rag/{study_id}
Path Parameters:
study_id(integer, required): The ID of the study
Query Parameters:
source_type(string, optional): Filter by source type (protocol,sap,tlf,csr_prev)q(string, optional): Search query (case-insensitive substring search in chunk text usingilike)limit(integer, optional): Maximum number of chunks to return (default: 50, max: 500, min: 1)offset(integer, optional): Number of chunks to skip (default: 0, min: 0)
Response Body:
{
"study_id": 0,
"source_type": "string | null",
"total_chunks": 0,
"limit": 50,
"offset": 0,
"chunks": [
{
"id": 0,
"study_id": 0,
"source_document_id": 0,
"source_type": "string",
"order_index": 0,
"text": "string",
"text_preview": "string",
"source_document_file_name": "string | null",
"created_at": "2024-01-01T00:00:00Z"
}
]
}Notes:
- Diagnostic endpoint to inspect RAG chunks
- Chunks are ordered by
source_typeandorder_index - Supports pagination with
limitandoffset - Returns
total_chunksfor implementing pagination on the client side - Text search (
q) performs case-insensitive substring matching text_previewcontains a truncated version of the text (first 200 characters) for list viewssource_document_file_namecontains the file name of the source document for convenience
Error Responses:
401/403: Missing or invalid token, or user is not a member of the study404: Study not found
Authorization: Requires
Authorization: Bearer <token>header. User must be a member of the study.
Method: POST
Path: /api/v1/qc/documents/{document_id}/run
Path Parameters:
document_id(integer, required): The ID of the Document (must be type "csr")
Response Body:
{
"document_id": 0,
"issues_created": 0,
"message": "string"
}Notes:
- Accepts a
Document.id(the Document withtype="csr") - Automatically resolves the linked
OutputDocumentand runs QC rules on it - If the
OutputDocumentdoesn't exist for the Document, it will be automatically created - Requires access to the study that owns the document
- Returns the number of issues created
- The same
document_idcan be used across CSR, QC, and RAG endpoints for consistent navigation
Error Responses:
400: Document type is not "csr"401/403: Missing or invalid token, or user is not a member of the study404: Document not found
Method: GET
Path: /api/v1/qc/issues/{study_id}
Path Parameters:
study_id(integer, required): The ID of the study
Query Parameters:
status(string, optional): Filter by status (open,resolved,wont_fix)severity(string, optional): Filter by severity (info,warning,error)
Response Body:
{
"issues": [
{
"id": 0,
"study_id": 0,
"document_id": 0,
"section_id": 0,
"rule_id": 0,
"severity": "string",
"status": "string",
"message": "string",
"created_at": "2024-01-01T00:00:00Z",
"resolved_at": "string | null",
"resolved_by": "string | null"
}
],
"total": 0
}Notes:
- Returns all QC issues for the study with optional filtering
- Issues are ordered by
created_atin descending order (most recent first) - Valid status values:
open,resolved,wont_fix - Valid severity values:
info,warning,error
Error Responses:
400: Invalid status or severity filter value401/403: Missing or invalid token, or user is not a member of the study404: Study not found
Method: GET
Path: /api/v1/qc/studies/{study_id}/issues
Path Parameters:
study_id(integer, required): The ID of the study
Query Parameters:
document_id(integer, optional): Filter by document IDstatus(string, optional): Filter by status (open,resolved,wont_fix)severity(string, optional): Filter by severity (info,warning,error)
Response Body:
{
"issues": [
{
"id": 0,
"study_id": 0,
"document_id": 0,
"section_id": 0,
"rule_id": 0,
"severity": "string",
"status": "string",
"message": "string",
"created_at": "2024-01-01T00:00:00Z",
"resolved_at": "string | null",
"resolved_by": "string | null"
}
],
"total": 0
}Notes:
- Returns all QC issues for the study with optional filtering
- Issues are ordered by
created_atin descending order (most recent first) document_idfilter allows filtering issues by a specific CSR document- Valid status values:
open,resolved,wont_fix - Valid severity values:
info,warning,error
Error Responses:
400: Invalid status or severity filter value401/403: Missing or invalid token, or user is not a member of the study404: Study not found
OutputDocument Terminology: The structured editable document for documents of type "csr" is called OutputDocument in the codebase and API. The term "CSR" (Clinical Study Report) refers specifically to the document type (Document.type == "csr"), while OutputDocument is the structured representation used for editing sections and versions. The /api/v1/output/* endpoints are the official API for OutputDocument operations. The deprecated /api/v1/csr/* endpoints are thin wrappers that delegate to the output endpoints and will be removed in a future major version.
The frontend can navigate by document_id for CSR, QC, and RAG operations. This provides a consistent identifier across all endpoints.
Key Concepts:
-
Document Model: The
Documentmodel represents a document in the system with fields:id: Unique document identifiertype: Document type (e.g.,"csr","protocol","ib","sap","tlf")study_id: The study this document belongs totitle,status, etc.
-
OutputDocument Model: The
OutputDocumentmodel represents the structured document for documents of type"csr"with:id: Unique OutputDocument identifierstudy_id: The study this OutputDocument belongs todocument_id: Link to theDocument(nullable, for backward compatibility)sections: List of OutputDocument sections
-
Relationship:
- A
Documentwithtype="csr"can have a linkedOutputDocument - The
OutputDocument.document_idfield links toDocument.id - When accessing OutputDocument endpoints by
document_id, the system automatically:- Finds the
Documentwithtype="csr" - Gets or creates the linked
OutputDocument - Returns the OutputDocument structure for editing
- Finds the
- A
Navigation Flow:
-
Frontend gets Document list:
GET /api/v1/studies/{study_id}/documents- Returns all documents for the study, including CSR documents
- Each CSR document has a
Document.idthat can be used for navigation
-
Frontend navigates to OutputDocument:
GET /api/v1/output/document/{document_id}- Uses the
Document.idfrom step 1 - Backend automatically resolves the linked
OutputDocument - Returns OutputDocument structure with sections
- Uses the
-
Frontend runs QC:
POST /api/v1/qc/documents/{document_id}/run- Uses the same
Document.id - Backend resolves
OutputDocumentand runs QC rules
- Uses the same
-
Frontend gets QC issues:
GET /api/v1/qc/studies/{study_id}/issues?document_id={document_id}- Filters issues by the same
Document.id
- Filters issues by the same
Backward Compatibility:
- Deprecated study-based endpoints (
GET /api/v1/csr/{study_id}) still work and use the same internal logic - They automatically find or create a default
Documentof type "csr" for the study - Both study-based and document-based endpoints use the same normalized OutputDocument loading function internally
- The
/api/v1/csr/*endpoints are deprecated wrappers that delegate to/api/v1/output/*endpoints
Consistent Behavior:
Both GET /api/v1/output/{study_id} and GET /api/v1/output/document/{document_id} use the same internal logic:
- Find or create a
Documentof type "csr" for the study - Get or create the linked
OutputDocument - Ensure default sections exist
- Return the OutputDocument structure
This ensures that regardless of which endpoint the frontend uses, the same OutputDocument structure is returned and the same document_id can be used for subsequent operations.
- All datetime fields are in ISO 8601 format (UTC)
- All endpoints return JSON responses unless otherwise specified (e.g., token form data input)
- Error responses follow the format:
{"detail": "error message"}