Business Process Monitoring & Management Platform
A REST API solution for real-time business process visualization, monitoring, and management.
The Process Dashboard API provides a platform for:
- Integration - RESTful design for seamless third-party integration
- Real-time Process Monitoring - Track business process execution across multiple systems
- Retry Management - Automated failure recovery with configurable retry strategies
- Security - Role-based API authentication with audit trails
- Data Retention - Configurable retention policies with GDPR-compliant data neutralization
- Pagination - Standardized pagination with RFC 8288 Link headers
- Features - Advanced filtering, search, and dashboard capabilities
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Client Apps │───>│ FastAPI Core │───>│ SQL Server │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
▼
┌──────────────────┐
│ Docker Runtime │
└──────────────────┘
Component | Technology | Purpose |
---|---|---|
API Framework | FastAPI 0.104+ | High-performance async web framework |
Data Layer | SQLModel + SQLAlchemy 2.0 | Type-safe ORM with event-driven automation |
Database | Microsoft SQL Server | Enterprise-grade persistence layer |
Authentication | Custom JWT + API Keys | Multi-tier security with role-based access |
Pagination | fastapi-pagination | RFC 8288 compliant pagination with Link headers |
Deployment | Docker + Compose | Containerized deployment and scaling |
Package Management | UV | Fast Python dependency resolution |
graph TD
A[Client Request] --> B{API Key Valid?}
B -->|No| C[401 Unauthorized]
B -->|Yes| D{Role Check}
D -->|Admin| E[Full Access]
D -->|User| F[Data Access Only]
E --> G[Resource Access]
F --> G[Resource Access]
G --> H[Audit Log]
- Docker Desktop 4.0+
- Git
- Network access to SQL Server instance
-
Clone and Configure
git clone https://github.com/AAK-MBU/Process_Dashboard_API.git cd Process_Dashboard_API # Configure environment cp .env.example .env # Edit .env with your database connection details
-
Deploy with Docker
docker-compose up -d
-
Run Database Migrations (if upgrading from older version)
# Connect to your SQL Server and run migration scripts sqlcmd -S your-server -d process_monitor -i scripts/migrations/001_add_soft_delete_and_retention.sql # Or use Azure Data Studio / SQL Server Management Studio # See scripts/migrations/README.md for details
-
Initialize Security
# Create admin API key docker exec process-visualization-api uv run python scripts/add_api_key_roles.py # Note the returned admin key for initial setup
-
Verify Installation
curl -H "X-API-Key: YOUR_ADMIN_KEY" \ http://localhost:8000/api/v1/auth/verify
# .env file example
DATABASE_URL=mssql+pyodbc://username:password@server/database?driver=ODBC+Driver+18+for+SQL+Server
DEBUG=false
CORS_ORIGINS='["http://localhost:3000","http://localhost:8080"]'
The API implements a two-tier role-based access control system:
Role | Capabilities | Use Cases |
---|---|---|
Administrator | • Full API access • API key management • User administration • System configuration |
System administrators, DevOps teams |
User | • Process data access • Read/write operations • Dashboard access • Monitoring capabilities |
Applications, end users, integrations |
POST /api/v1/api-keys/
X-API-Key: {ADMIN_KEY}
Content-Type: application/json
{
"name": "Production Application Key",
"description": "API key for production dashboard application",
"role": "user",
"expires_at": "2025-12-31T23:59:59Z"
}
Response:
{
"id": 5,
"name": "Production Application Key",
"role": "user",
"key_prefix": "Secrect_key_prefix",
"key": "Secrect_key_",
"created_at": "2025-10-03T10:30:00Z",
"expires_at": "2025-12-31T23:59:59Z"
}
Security Notice: API keys are only displayed once during creation. Store them securely.
GET /api/v1/api-keys/
X-API-Key: {ADMIN_KEY}
DELETE /api/v1/api-keys/{key_id}
X-API-Key: {ADMIN_KEY}
GET /api/v1/auth/verify
X-API-Key: {API_KEY}
Response:
{
"status": "valid",
"message": "API key is valid",
"key_info": {
"id": 5,
"name": "Production Application Key",
"role": "user",
"usage_count": 127,
"last_used_at": "2025-10-03T10:29:45Z",
"is_active": true
}
}
GET /api/v1/auth/me
X-API-Key: {API_KEY}
The API implements comprehensive data retention and privacy management capabilities to support GDPR compliance and data lifecycle management.
- Soft Delete - Recoverable deletion with retention periods. Soft-deleted items are automatically hidden from all API responses.
- Configurable Retention - Set custom retention periods per process (e.g., 6, 12, 48 months)
- Automatic Neutralization - Scheduled removal of personally identifiable information (PII)
- Audit Trail - Complete tracking of all deletion and neutralization operations
PUT /api/v1/processes/{process_id}/retention
X-API-Key: {ADMIN_KEY}
Content-Type: application/json
{
"retention_months": 12
}
Common Retention Periods:
6
- 6 months (short-term operational data)12
- 1 year (standard business processes)48
- 4 years (regulatory compliance requirements)null
- No automatic retention (manual cleanup only)
DELETE /api/v1/processes/{process_id}
X-API-Key: {ADMIN_KEY}
Behavior:
- Process marked as deleted (not permanently removed)
- All associated runs and steps marked as deleted
- Soft-deleted items are automatically hidden from all list endpoints and queries
- Accessing a soft-deleted item by ID returns 404 (not found)
- Data remains recoverable until retention period expires
- Use the restore endpoint to make the process visible again
POST /api/v1/processes/{process_id}/restore
X-API-Key: {ADMIN_KEY}
DELETE /api/v1/runs/{run_id}
X-API-Key: {ADMIN_KEY}
POST /api/v1/runs/{run_id}/restore
X-API-Key: {ADMIN_KEY}
POST /api/v1/runs/{run_id}/neutralize
X-API-Key: {ADMIN_KEY}
Neutralization Process:
entity_id
- Replaced with "NEUTRALIZED_{run_id}"entity_name
- Set to nullmeta
- Sensitive fields removed from JSON metadatais_neutralized
- Flag set to true (irreversible)
Example Before:
{
"entity_id": "12345678-1234",
"entity_name": "John Doe",
"meta": {
"email": "john.doe@example.com",
"phone": "+45 12 34 56 78",
"address": "Main Street 123"
}
}
Example After:
{
"entity_id": "NEUTRALIZED_42",
"entity_name": null,
"meta": {
"department": "Sales",
"region": "EMEA"
},
"is_neutralized": true
}
GET /api/v1/admin/cleanup/stats?limit=10
X-API-Key: {ADMIN_KEY}
Response:
{
"total_due_for_neutralization": 150,
"sample_run_ids": [42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
}
POST /api/v1/admin/cleanup/neutralize?limit=100&dry_run=false
X-API-Key: {ADMIN_KEY}
Parameters:
limit
- Maximum number of runs to process (default: 100, max: 1000)dry_run
- Preview only without making changes (default: true)
Response:
{
"neutralized_count": 87,
"failed_count": 0,
"neutralized_run_ids": [42, 43, 44, ...],
"failed_run_ids": []
}
Process Run Created
│
▼
[scheduled_deletion_at = created_at + retention_months]
│
▼
Time Passes
│
▼
[scheduled_deletion_at reached]
│
▼
Admin Triggers Cleanup
│
▼
[PII Neutralized - Irreversible]
│
▼
[Run retained for statistical/audit purposes]
GET /api/v1/processes/
X-API-Key: {API_KEY}
# Pagination Parameters
?page=1&size=50
Response:
{
"items": [
{
"id": 1,
"name": "Customer Onboarding",
"meta": {...},
"created_at": "2025-10-03T10:00:00Z",
"updated_at": "2025-10-03T10:00:00Z"
}
],
"total": 150,
"page": 1,
"size": 50,
"pages": 3
}
Response Headers:
Link: <http://api/v1/processes/?page=1&size=50>; rel="first",
<http://api/v1/processes/?page=3&size=50>; rel="last",
<http://api/v1/processes/?page=2&size=50>; rel="next"
X-Total-Count: 150
X-Page: 1
X-Page-Size: 50
X-Total-Pages: 3
Pagination Parameters:
page
- Page number (default: 1, minimum: 1)size
- Items per page (default: 50, maximum: 100)
Note: Only returns active (non-deleted) processes. Soft-deleted processes are automatically excluded.
POST /api/v1/processes/
X-API-Key: {API_KEY}
Content-Type: application/json
{
"name": "Customer Onboarding",
"meta": {
"description": "Automated customer onboarding workflow",
"department": "Customer Success",
"owner": "system.automation",
"version": "2.1.0"
}
}
GET /api/v1/processes/{process_id}
X-API-Key: {API_KEY}
POST /api/v1/runs/
X-API-Key: {API_KEY}
Content-Type: application/json
{
"process_id": 1,
"entity_id": "CUST-20251003-001",
"entity_name": "Acme Corporation",
"meta": {
"initiated_by": "api.integration",
"priority": "high",
"environment": "production",
"correlation_id": "req-20251003-12345"
}
}
GET /api/v1/runs/search?q={search_term}&process_id={id}
X-API-Key: {API_KEY}
# Search examples
?q=Viby&process_id=1&page=1&size=50 # Search in specific process
?q=completed # Search across all processes
?q=12345678 # Search by entity ID
Response:
{
"items": [
{
"run": {
"id": 42,
"process_id": 1,
"entity_id": "CUST-001",
"entity_name": "Viby Clinic",
"status": "completed",
"meta": {
"clinic": "Viby",
"region": "Central"
}
},
"matches": [
{
"field": "entity_name",
"value": "Viby Clinic"
},
{
"field": "meta.clinic",
"value": "Viby"
}
]
}
],
"total": 15,
"page": 1,
"size": 50,
"pages": 1
}
Features:
- Automatic field detection - Searches across all relevant fields
- Match annotations - Shows which fields matched and their values
- Case-insensitive - Finds matches regardless of case
- Partial matching - Finds substrings within values
- Standard fields - Always searches:
entity_id
,entity_name
,status
- Metadata fields - Searches custom fields when
process_id
is provided
Query Parameters:
q
- Search term (required, minimum 1 character)process_id
- Optional process ID to search within and include metadata fieldspage
- Page number (default: 1)size
- Items per page (default: 50, maximum: 100)
GET /api/v1/runs/
X-API-Key: {API_KEY}
# Advanced Filtering with Pagination
?entity_name=Acme
&status=completed
&created_after=2025-10-01T00:00:00Z
&created_before=2025-10-31T23:59:59Z
&meta_filter=priority:high
&meta_filter=environment:production
&order_by=created_at
&sort_direction=desc
&page=1
&size=50
Response:
{
"items": [
{
"id": 42,
"process_id": 1,
"entity_id": "CUST-20251003-001",
"entity_name": "Acme Corporation",
"status": "completed",
"meta": {...},
"started_at": "2025-10-03T10:00:00Z",
"finished_at": "2025-10-03T10:15:00Z"
}
],
"total": 250,
"page": 1,
"size": 50,
"pages": 5
}
Query Parameters:
- Filters:
process_id
- Filter by process IDentity_id
- Filter by entity IDentity_name
- Partial match on entity namestatus
- Filter by run statusstarted_after
,started_before
- Date filters (ISO 8601 format)finished_after
,finished_before
- Date filters (ISO 8601 format)meta_filter
- Filter by metadata (format:field:value
). Can be specified multiple times for multiple filters. Multiple values for the same field are OR'd together, different fields are AND'd together.
- Sorting:
order_by
- Field to sort by (default:created_at
)sort_direction
- Sort direction:asc
ordesc
(default:desc
)
- Pagination:
page
- Page number (default: 1)size
- Items per page (default: 50, maximum: 100)
Note: Only returns active (non-deleted) runs. Soft-deleted runs are automatically excluded from results.
💡 Tip: Use Global Search for quick lookups when you don't know the exact field, or Query Process Runs for precise filtering with multiple criteria and sorting.
📚 Detailed Documentation: See documentation/search_with_matches.md for comprehensive search examples, implementation details, and client code samples.
GET /api/v1/runs/{run_id}
X-API-Key: {API_KEY}
GET /api/v1/steps/process/{process_id}
X-API-Key: {API_KEY}
Note: Only returns active (non-deleted) steps. Soft-deleted steps are automatically excluded.
GET /api/v1/steps/process/{process_id}/rerunnable
X-API-Key: {API_KEY}
Note: Only returns active (non-deleted) steps. Soft-deleted steps are automatically excluded.
PATCH /api/v1/step-runs/{step_run_id}
X-API-Key: {API_KEY}
Content-Type: application/json
{
"status": "success",
"started_at": "2025-10-03T10:15:00Z",
"finished_at": "2025-10-03T10:17:30Z",
"meta": {
"processing_time_ms": 150000,
"records_processed": 1247,
"error_details": null,
"output_location": "s3://bucket/outputs/run-123.json"
}
}
Note: When a step run's status is updated, the parent process run's status is automatically recalculated and updated based on all step statuses. This is handled by SQLAlchemy event handlers.
POST /api/v1/step-runs/{step_run_id}/rerun
X-API-Key: {API_KEY}
Content-Type: application/json
{
"meta": {
"rerun_reason": "Temporary network issue resolved",
"initiated_by": "admin.user",
"rerun_strategy": "full_retry"
}
}
Response:
{
"id": 156,
"original_step_run_id": 123,
"status": "pending",
"created_at": "2025-10-03T10:30:00Z",
"message": "Step rerun initiated successfully"
}
Process steps can be configured for intelligent rerun capabilities:
{
"is_rerunnable": true,
"rerun_config": {
"subprocess_id": "data_extraction_v2",
"api_endpoint": "https://api.external-system.com/process",
"timeout_seconds": 300,
"max_retries": 3,
"retry_delays": [60, 120, 300],
"failure_conditions": ["network_error", "timeout", "rate_limit"]
}
}
sequenceDiagram
participant U as User
participant API as Process API
participant DB as Database
participant EXT as External System
U->>API: POST /step-runs/{id}/rerun
API->>DB: Validate step is rerunnable
API->>DB: Create new StepRun record
API->>EXT: Execute configured subprocess
EXT-->>API: Return results
API->>DB: Update step status
API->>U: Return rerun status
GET /api/v1/dashboard/process/{process_id}
X-API-Key: {API_KEY}
Note: Dashboard only shows active (non-deleted) runs. Soft-deleted runs are automatically excluded.
Response:
{
"process": {
"id": 1,
"name": "Customer Onboarding",
"total_steps": 8
},
"runs": [
{
"id": 123,
"entity_name": "Acme Corp",
"status": "completed",
"success_rate": 87.5
}
],
"statistics": {
"total_runs": 1247,
"completed_runs": 1156,
"failed_runs": 45,
"running_runs": 46,
"average_completion_time": "00:14:32"
}
}
The API implements state management using SQLAlchemy 2.0 event handlers to automatically maintain data consistency.
When any step run's status changes, the parent process run's status is automatically recalculated based on all step statuses:
- Running - If any step is currently running
- Failed - If any step has failed (and none are running)
- Completed - If all steps are successful
- Pending - Initial state (no steps started yet)
How it works:
sequenceDiagram
participant Client
participant API
participant EventHandler
participant Database
Client->>API: PATCH /step-runs/123 {status: "failed"}
API->>Database: Update step_run.status
Database->>EventHandler: before_commit event
EventHandler->>Database: Query all step statuses for parent run
EventHandler->>Database: Recalculate and update run.status
Database->>Client: Return updated step_run
Note over EventHandler: Parent status automatically updated!
When creating step runs, the step_index
field is automatically populated from the related process step's index:
// Client sends (no step_index required)
{
"run_id": 42,
"step_id": 5,
"status": "pending"
}
// Database receives (step_index auto-populated)
{
"run_id": 42,
"step_id": 5,
"step_index": 3, // ← Automatically set from ProcessStep.index
"status": "pending"
}
Implementation:
- Uses SQLAlchemy
before_insert
event handler - Queries related
ProcessStep
to retrieve the correct index - Ensures step runs maintain proper ordering without manual tracking
Event Lifecycle:
- Client makes API request (create/update step run)
- API validates and prepares database changes
- Before Insert - step_index is auto-populated
- Database flush occurs
- Before Commit - parent run status is recalculated
- Transaction commits with all changes
- Client receives response with auto-updated data
Create a .env
file in your project root:
# Database Configuration
DATABASE_URL=mssql+pyodbc://username:password@server/database?driver=ODBC+Driver+17+for+SQL+Server
# Application Configuration
DEBUG=false
API_V1_PREFIX=/api/v1
# CORS Configuration
CORS_ORIGINS=["http://localhost:3000","https://dashboard.company.com"]
For Docker deployments, use environment variables:
# docker-compose.yml
version: '3.8'
services:
api:
build: .
environment:
- DATABASE_URL=mssql+pyodbc://sa:YourPassword@db/ProcessDashboard?driver=ODBC+Driver+18+for+SQL+Server
- DEBUG=false
- CORS_ORIGINS=["http://localhost:3000"]
ports:
- "8000:8000"
depends_on:
- db
restart: unless-stopped
db:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
- SA_PASSWORD=YourPassword123!
- ACCEPT_EULA=Y
ports:
- "1433:1433"
# Clone repository
git clone https://github.com/AAK-MBU/Process_Dashboard_API.git
cd Process_Dashboard_API
# Setup virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install dependencies with uv
uv sync
# Setup environment
cp .env.example .env
# Edit .env with your configuration
# Initialize database
python scripts/migrate_db.py
# Start development server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
- Docker & Docker Compose
- SQL Server (2019+ recommended)
- SSL Certificate (for HTTPS)
- Load Balancer (for high availability)
-- Create database
CREATE DATABASE ProcessDashboard;
-- Create application user
CREATE LOGIN process_api WITH PASSWORD = 'SecurePassword123!';
USE ProcessDashboard;
CREATE USER process_api FOR LOGIN process_api;
EXEC sp_addrolemember 'db_datareader', 'process_api';
EXEC sp_addrolemember 'db_datawriter', 'process_api';
EXEC sp_addrolemember 'db_ddladmin', 'process_api';
# Create first admin API key
docker exec -it process-dashboard-api python -c "
from app.database import get_session
from app.models import ApiKey
session = next(get_session())
admin_key = ApiKey.create_key(
name='Initial Admin Key',
description='Bootstrap admin key for initial setup',
role='admin'
)
session.add(admin_key)
session.commit()
print(f'Admin Key: {admin_key.key}')
"
All list endpoints support standardized pagination using the page
and size
query parameters. The API returns pagination metadata in both the response body and HTTP headers.
The following endpoints support pagination:
GET /api/v1/processes/
- List all processesGET /api/v1/runs/
- List all process runsGET /api/v1/admin/api-keys/
- List all API keys (admin only)
Parameter | Type | Default | Maximum | Description |
---|---|---|---|---|
page |
integer | 1 | - | Page number (1-indexed) |
size |
integer | 50 | 100 | Number of items per page |
All paginated endpoints return a consistent response structure:
{
"items": [
{
"id": 1,
"...": "..."
}
],
"total": 150,
"page": 1,
"size": 50,
"pages": 3
}
Fields:
items
- Array of data objects for the current pagetotal
- Total number of records matching the querypage
- Current page numbersize
- Number of items per pagepages
- Total number of pages available
The Link
header provides navigation URLs for pagination:
Link: <http://api/v1/resource?page=1&size=50>; rel="first",
<http://api/v1/resource?page=5&size=50>; rel="last",
<http://api/v1/resource?page=1&size=50>; rel="prev",
<http://api/v1/resource?page=3&size=50>; rel="next"
Relations:
first
- URL to the first pagelast
- URL to the last pageprev
- URL to the previous page (omitted if on first page)next
- URL to the next page (omitted if on last page)
Note: The Link header preserves all query parameters (filters, sorting, etc.) for easy navigation.
Additional headers provide quick access to pagination metadata:
Header | Description | Example |
---|---|---|
X-Total-Count |
Total number of records | 150 |
X-Page |
Current page number | 2 |
X-Page-Size |
Items per page | 50 |
X-Total-Pages |
Total number of pages | 3 |
GET /api/v1/processes/?page=1&size=25
X-API-Key: {API_KEY}
GET /api/v1/runs/?page=2&size=10&process_id=1&order_by=created_at&sort_direction=desc
X-API-Key: {API_KEY}
GET /health
Response:
{
"status": "healthy",
"timestamp": "2025-10-03T10:30:00Z",
"version": "1.0.0",
"database": "connected",
"uptime": "7d 12h 30m"
}
GET /
Response:
{
"name": "Process Dashboard API",
"version": "1.0.0",
"description": "Enterprise process monitoring and management API",
"environment": "production",
"features": [
"api_key_authentication",
"role_based_access",
"step_rerun_system",
"metadata_filtering"
]
}
GET /api/v1/auth/usage-stats
X-API-Key: {API_KEY}
Response:
{
"key_id": 5,
"usage_count": 1247,
"last_used_at": "2025-10-03T10:29:45Z",
"daily_usage": 156,
"weekly_usage": 892,
"top_endpoints": [
{"endpoint": "/api/v1/runs/", "count": 45},
{"endpoint": "/api/v1/processes/", "count": 12}
]
}
# Test database connectivity
docker exec -it process-dashboard-api python -c "
from app.database import engine
try:
engine.connect()
print('Database connection successful')
except Exception as e:
print(f'Database connection failed: {e}')
"
Solutions:
- Verify connection string format
- Check SQL Server authentication mode
- Ensure ODBC driver 18+ is installed
- Verify network connectivity and firewall rules
{
"detail": "Invalid or expired API key"
}
Debugging Steps:
- Verify key format starts with
pd_
- Check key expiration date with
/api/v1/auth/verify
- Verify key is active in database
- Check rate limiting status
# Manual migration execution
docker exec -it process-dashboard-api python scripts/migrate_db.py
# Add role support to existing installation
docker exec -it process-dashboard-api python scripts/add_api_key_roles.py
Common Fixes:
- Ensure database user has DDL permissions
- Check for table name conflicts
- Verify SQL Server compatibility mode
{
"detail": "Descriptive error message",
"status_code": 400,
"error_type": "validation_error",
"timestamp": "2025-10-03T10:30:00Z"
}
- 200 - Success
- 201 - Created
- 400 - Bad Request / Validation Error
- 401 - Invalid or Missing API Key
- 403 - Insufficient Permissions (Wrong Role)
- 404 - Resource Not Found
- 422 - Unprocessable Entity
- 429 - Rate Limited
- 500 - Internal Server Error
-
Storage Security
- Never log full API keys
- Use secure key management systems
- Implement key escrow for critical operations
-
Access Control
- Follow principle of least privilege
- Separate admin and user keys by function
- Regular access reviews and audits
- HTTPS Only
- Force SSL/TLS encryption
- Use HSTS headers
- Implement certificate pinning
When the server is running, access:
- Swagger UI:
http://localhost:8000/docs
- ReDoc:
http://localhost:8000/redoc
- OpenAPI Schema:
http://localhost:8000/openapi.json
# Filter by custom metadata fields
# Multiple filters: same field = OR, different fields = AND
GET /api/v1/runs/?meta_filter=department:Sales&meta_filter=priority:high&meta_filter=region:EMEA
X-API-Key: {API_KEY}
# Example: Filter by multiple values for the same field (OR logic)
GET /api/v1/runs/?meta_filter=department:Sales&meta_filter=department:Marketing
X-API-Key: {API_KEY}
# Values can contain commas
GET /api/v1/runs/?meta_filter=company:Acme, Inc&meta_filter=status:active
X-API-Key: {API_KEY}
# Filter by time periods
GET /api/v1/runs/?created_after=2025-10-01T00:00:00Z&created_before=2025-10-31T23:59:59Z
X-API-Key: {API_KEY}
# Get available filterable fields for a process
GET /api/v1/processes/{process_id}/searchable-fields
X-API-Key: {API_KEY}
The project uses semantic versioning (MAJOR.MINOR.PATCH) with centralized version management.
Version Location: pyproject.toml
(single source of truth)
Use the Python script to update version across all project files:
# Bump patch version (1.0.0 -> 1.0.1)
python scripts/update_version.py patch
# Bump minor version (1.0.0 -> 1.1.0)
python scripts/update_version.py minor
# Bump major version (1.0.0 -> 2.0.0)
python scripts/update_version.py major
# Set specific version
python scripts/update_version.py 2.1.0
Updated Files:
pyproject.toml
- Project metadata.env.example
- Environment templateDockerfile
- Container configuration
- API Reference: Interactive Docs
- FastAPI Documentation: https://fastapi.tiangolo.com/
- SQLModel Guide: https://sqlmodel.tiangolo.com/
- Docker Best Practices: https://docs.docker.com/develop/best-practices/
This project is licensed under the MIT License - see the LICENSE file for details.