API Reference¶
Complete reference for all REST API endpoints exposed by the backend server. All routes are prefixed with /api/v1. For details on how the frontend calls these endpoints, see the Frontend API Integration page.
Overview¶
| Group | Prefix | Endpoints | Auth Required |
|---|---|---|---|
| Authentication | /api/v1/auth |
3 | Partial |
| Users | /api/v1/users |
7 | Yes |
| Documents | /api/v1/docs |
5 | Yes |
| Virtual Paths | /api/v1/paths |
4 | Yes |
Authorization Levels¶
The API uses a dependency injection chain for authorization. For a detailed breakdown of how this chain works, see the Security & Auth page.
| Level | Dependency | Description |
|---|---|---|
| None | -- | Public endpoint |
| CurrentUser | get_current_user |
Valid JWT token required |
| ActiveUser | get_current_active_user |
Token + is_active=True |
| AdminUser | RoleChecker(role_id=99) |
Token + active + role_id=99 |
Authentication¶
POST /api/v1/auth/register¶
Register a new user account. No authentication required.
Auth: None
Request Body:
{
"full_name": "John Doe",
"email": "john@example.com",
"username": "johndoe",
"password": "20-Na$$aQ-26"
}
| Field | Type | Constraints | Required |
|---|---|---|---|
full_name |
string |
6-60 chars | Yes |
email |
string |
Valid email, max 50 chars | Yes |
username |
string |
3-20 chars | No (auto-generated from email if omitted) |
password |
string |
8-64 chars, must contain a digit and a special character | Yes |
Response: 201 Created
{
"user_id": 1,
"full_name": "John Doe",
"email": "john@example.com",
"username": "johndoe",
"role": null,
"is_active": false,
"created_at": "2026-02-26T14:30:00"
}
Account Activation Required
Newly registered users have is_active=false and role=null. An admin must activate the user and assign a role before they can log in.
Error Responses:
| Status | Detail |
|---|---|
400 |
Email already registered |
400 |
Username already taken |
POST /api/v1/auth/login¶
Authenticate with email and password using OAuth2 password flow.
Auth: None
Request Body: application/x-www-form-urlencoded
| Field | Description |
|---|---|
username |
Email address (OAuth2 spec uses "username" field) |
password |
User password |
curl -X POST http://localhost:8000/api/v1/auth/login \
-d "username=john@example.com&password=20-Na$$aQ-26"
Response: 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer"
}
Error Responses:
| Status | Detail |
|---|---|
401 |
Incorrect email or password |
403 |
User is not active |
POST /api/v1/auth/refresh¶
Refresh an access token using a valid refresh token.
Auth: Bearer token (refresh token)
Request: Send the refresh token as a Bearer token in the Authorization header.
Response: 200 OK
Error Responses:
| Status | Detail |
|---|---|
401 |
Invalid refresh token |
401 |
Invalid token type (access token was provided instead) |
401 |
User not found |
Users¶
GET /api/v1/users/¶
List all users with pagination.
Auth: AdminUser
Query Parameters:
| Parameter | Type | Default | Constraints | Description |
|---|---|---|---|---|
skip |
int |
0 |
>= 0 | Records to skip |
limit |
int |
20 |
1-100 | Max records to return |
Response: 200 OK -- Array of UserResponse
GET /api/v1/users/pending¶
List users pending role assignment (is_active=false).
Auth: AdminUser
Query Parameters:
| Parameter | Type | Default | Constraints |
|---|---|---|---|
limit |
int |
20 |
1-50 |
Response: 200 OK -- Array of UserResponse
GET /api/v1/users/me¶
Get the current authenticated user's profile.
Auth: CurrentUser
Response: 200 OK
{
"user_id": 1,
"full_name": "John Doe",
"email": "john@example.com",
"username": "johndoe",
"role": "Admin",
"is_active": true,
"created_at": "2026-02-26T14:30:00"
}
PATCH /api/v1/users/me¶
Update the current user's profile (name and username only).
Auth: ActiveUser
Request Body:
| Field | Type | Constraints | Required |
|---|---|---|---|
full_name |
string |
6-60 chars | No |
username |
string |
3-20 chars | No |
Response: 200 OK -- Updated UserResponse
Error Responses:
| Status | Detail |
|---|---|
400 |
No fields to update |
400 |
Username already exists |
PATCH /api/v1/users/{user_id}¶
Update any user's profile (admin only).
Auth: AdminUser
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
user_id |
int |
Target user ID |
Request Body:
{
"full_name": "Updated Name",
"email": "new@example.com",
"username": "newusername",
"role_id": 2,
"is_active": true
}
| Field | Type | Required |
|---|---|---|
full_name |
string |
No |
email |
string (email) |
No |
username |
string |
No |
role_id |
int |
No |
is_active |
bool |
No |
Response: 200 OK -- Updated UserResponse
Error Responses:
| Status | Detail |
|---|---|
400 |
Email/username already in use |
400 |
Role does not exist |
404 |
User not found |
PATCH /api/v1/users/{user_id}/activate¶
Activate a user (set is_active=true). No request body allowed.
Auth: AdminUser
Response: 200 OK -- Updated UserResponse
Error Responses:
| Status | Detail |
|---|---|
400 |
Request body is not allowed |
404 |
User not found |
DELETE /api/v1/users/{user_id}¶
Delete a user account.
Auth: AdminUser
Response: 204 No Content
Error Responses:
| Status | Detail |
|---|---|
400 |
Cannot delete your own account |
400 |
Cannot delete user (foreign key constraints) |
404 |
User not found |
Documents¶
POST /api/v1/docs/upload¶
Upload a file for OCR processing.
Auth: ActiveUser
Request: multipart/form-data
| Field | Type | Description |
|---|---|---|
file |
File |
The file to upload |
description |
string (form) |
Optional description |
full_path |
string (form) |
Virtual path (default: root "") |
curl -X POST http://localhost:8000/api/v1/docs/upload \
-H "Authorization: Bearer <token>" \
-F "file=@document.pdf" \
-F "full_path=finance/reports"
Processing Flow:
sequenceDiagram
participant Client
participant Server
participant Blob as Azure Blob
participant DB as SQL Server
participant RMQ as RabbitMQ
Client->>Server: POST /docs/upload (file + metadata)
Server->>Server: Validate path exists
Server->>Server: Check file size (max 50 MB)
Server->>Blob: Upload file
Blob-->>Server: Blob URL
Server->>DB: Create Documents record
Server->>DB: Create ProcessingStatus (Queued)
Server->>RMQ: Publish to ocr_queue
Server->>DB: Commit transaction
Server-->>Client: 201 Created
Response: 201 Created
{
"filename": "document.pdf",
"path": "https://account.blob.core.windows.net/container/finance/reports/document.pdf",
"ctype": "application/pdf",
"size": 1048576,
"metadata": {
"description": null,
"full_path": "finance/reports"
}
}
Error Responses:
| Status | Detail |
|---|---|
404 |
Provided path doesn't exist |
409 |
Document with this filename already exists at the specified path |
413 |
File too large (max 50 MB) |
500 |
Storage upload failed |
Transactional Safety
If the database commit fails after blob upload, the server automatically deletes the uploaded blob to prevent orphaned files.
GET /api/v1/docs/¶
List all documents with pagination and filtering (admin only).
Auth: AdminUser
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
skip |
int |
0 |
Records to skip |
limit |
int |
20 |
Max records (1-100) |
status |
string |
null |
Filter: Finished, Failed, Processing, or Queued |
user_id |
int |
null |
Filter by uploader |
Response: 200 OK
{
"total": 42,
"items": [
{
"doc_id": 1,
"filename": "report.pdf",
"path": "/finance",
"uploaded_by_user_id": 7,
"uploaded_at": "2026-02-26T14:30:00",
"status": "Finished",
"error_message": null
}
]
}
GET /api/v1/docs/me¶
List the current user's documents with pagination and filtering.
Auth: ActiveUser
Query Parameters: Same as GET /api/v1/docs/ except user_id (automatically scoped to current user).
Response: 200 OK -- Same format as above.
GET /api/v1/docs/{doc_id}/status¶
Check the OCR processing status for a specific document. Only returns results for documents owned by the current user.
Auth: ActiveUser
Response: 200 OK
{
"doc_id": 1,
"filename": "report.pdf",
"stage_name": "OCR",
"status": "Finished",
"start_time": "2026-02-26T14:30:05",
"end_time": "2026-02-26T14:30:12",
"error_message": null
}
Status Values:
| Status | Description |
|---|---|
Queued |
Waiting in RabbitMQ for OCR worker |
Processing |
OCR worker is actively processing |
Finished |
OCR completed successfully |
Failed |
OCR failed (see error_message) |
DELETE /api/v1/docs/{doc_id}¶
Delete a document's database records and its blob from storage.
Auth: AdminUser
Response: 200 OK
Error Responses:
| Status | Detail |
|---|---|
404 |
Document not found |
409 |
Cannot delete while document is Queued or Processing |
Blob Cleanup
If the database records are deleted successfully but the blob deletion fails, the request still succeeds. The orphaned blob can be cleaned up separately.
Virtual Paths¶
Virtual paths represent a folder hierarchy in Azure Blob Storage. They are stored in the Virtual_Paths table and used to organize uploaded documents.
GET /api/v1/paths/¶
List all virtual paths with depth filtering and prefix search.
Auth: ActiveUser
Query Parameters:
| Parameter | Type | Default | Constraints | Description |
|---|---|---|---|---|
minDepth |
int |
0 |
0-10 | Minimum folder depth |
maxDepth |
int |
5 |
1-30 | Maximum folder depth |
prefix |
string |
"" |
-- | Path prefix filter |
Response: 200 OK
[
{
"id": 1,
"full_path": "/",
"description": "Root directory",
"depth": 0,
"created_at": "2026-01-01T00:00:00"
},
{
"id": 2,
"full_path": "finance/reports",
"description": "Financial reports",
"depth": 2,
"created_at": "2026-02-15T10:00:00"
}
]
POST /api/v1/paths/¶
Create a new virtual path.
Auth: AdminUser
Request Body:
Response: 201 Created -- PathResponse
The depth is calculated automatically from the number of path segments.
Error Responses:
| Status | Detail |
|---|---|
409 |
Path already exists |
PATCH /api/v1/paths/{path_id}¶
Update an existing path's full_path and/or description.
Auth: AdminUser
Request Body:
Response: 200 OK -- Updated PathResponse
Error Responses:
| Status | Detail |
|---|---|
404 |
Path not found |
409 |
New path already exists |
DELETE /api/v1/paths/{path_id}¶
Delete a virtual path.
Auth: AdminUser
Response: 204 No Content
Error Responses:
| Status | Detail |
|---|---|
400 |
Cannot delete (documents reference this path) |
404 |
Path not found |
Common Error Format¶
All error responses follow this format:
Authentication errors include a WWW-Authenticate header:
Schemas Reference¶
UserResponse¶
{
"user_id": "int",
"full_name": "string",
"email": "string (email)",
"username": "string",
"role": "string | null",
"is_active": "bool",
"created_at": "datetime"
}
TokenLogin¶
TokenRefresh¶
DocumentListItem¶
{
"doc_id": "int",
"filename": "string",
"path": "string",
"uploaded_by_user_id": "int",
"uploaded_at": "datetime",
"status": "string | null",
"error_message": "string | null"
}
DocumentListResponse¶
FileUploadResponse¶
{
"filename": "string",
"path": "string (URL or path)",
"ctype": "string (MIME type)",
"size": "int (bytes)",
"metadata": {
"description": "string | null",
"full_path": "string"
}
}