How to use the REST API
This guide shows you how to use the current REST API endpoints that are available in the Panels system.
Prerequisites
- Access to the API server (typically running on port 3001)
- Understanding of REST APIs and HTTP methods
- Valid tenant ID and user ID for multi-tenant requests
Current API Endpoints
Based on the current implementation, here are the available endpoints:
Panel Management
List Panels
GET /api/panels?tenantId={tenantId}&userId={userId}
Example Request:
curl -X GET "http://localhost:3001/api/panels?tenantId=tenant-123&userId=user-456" \
-H "Content-Type: application/json"
Response:
[
{
"id": 1,
"name": "Patient Care Panel",
"description": "Main patient management panel",
"tenantId": "tenant-123",
"userId": "user-456",
"cohortRule": {
"conditions": [],
"logic": "AND"
},
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z"
}
]
Get Panel by ID
GET /api/panels/{id}?tenantId={tenantId}&userId={userId}
Example Request:
curl -X GET "http://localhost:3001/api/panels/1?tenantId=tenant-123&userId=user-456" \
-H "Content-Type: application/json"
Response:
{
"id": 1,
"name": "Patient Care Panel",
"description": "Main patient management panel",
"tenantId": "tenant-123",
"userId": "user-456",
"cohortRule": {
"conditions": [],
"logic": "AND"
},
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z"
}
Create Panel
POST /api/panels
Request Body:
{
"name": "New Patient Panel",
"description": "Panel for managing new patients",
"tenantId": "tenant-123",
"userId": "user-456"
}
Example Request:
curl -X POST "http://localhost:3001/api/panels" \
-H "Content-Type: application/json" \
-d '{
"name": "New Patient Panel",
"description": "Panel for managing new patients",
"tenantId": "tenant-123",
"userId": "user-456"
}'
Response (201 Created):
{
"id": 2,
"name": "New Patient Panel",
"description": "Panel for managing new patients",
"tenantId": "tenant-123",
"userId": "user-456",
"cohortRule": {
"conditions": [],
"logic": "AND"
},
"createdAt": "2024-01-15T14:30:00Z",
"updatedAt": "2024-01-15T14:30:00Z"
}
Update Panel
PUT /api/panels/{id}
Request Body:
{
"name": "Updated Panel Name",
"description": "Updated description",
"tenantId": "tenant-123",
"userId": "user-456"
}
Delete Panel
DELETE /api/panels/{id}
Request Body:
{
"tenantId": "tenant-123",
"userId": "user-456"
}
Data Source Management
List Data Sources for Panel
GET /api/panels/{panelId}/datasources
Create Data Source
POST /api/panels/{panelId}/datasources
Request Body:
{
"type": "fhir",
"config": {
"baseUrl": "https://api.medplum.com/fhir/R4/",
"clientId": "your-client-id"
},
"tenantId": "tenant-123",
"userId": "user-456"
}
Sync Data Source
POST /api/datasources/{datasourceId}/sync
Column Management
List Columns for Panel
GET /api/panels/{panelId}/columns
Create Base Column
POST /api/panels/{panelId}/columns/base
Request Body:
{
"name": "Patient Name",
"fhirPath": "Patient.name.given[0] + ' ' + Patient.name.family",
"type": "string",
"properties": {},
"metadata": {},
"tenantId": "tenant-123",
"userId": "user-456"
}
Create Calculated Column
POST /api/panels/{panelId}/columns/calculated
Request Body:
{
"name": "Age",
"type": "number",
"formula": "DATEDIFF('year', Patient.birthDate, NOW())",
"dependencies": ["Patient.birthDate"],
"properties": {},
"metadata": {},
"tenantId": "tenant-123",
"userId": "user-456"
}
View Management
List Views
GET /api/views?tenantId={tenantId}&userId={userId}
Get View by ID
GET /api/views/{viewId}?tenantId={tenantId}&userId={userId}
Create View
POST /api/views
Request Body:
{
"name": "High Priority Patients",
"panelId": 1,
"config": {
"columns": ["patient_name", "age", "last_visit"],
"groupBy": [],
"layout": "table"
},
"tenantId": "tenant-123",
"userId": "user-456"
}
Response (201 Created):
{
"id": 10,
"name": "High Priority Patients",
"description": "",
"panelId": 1,
"userId": "user-456",
"tenantId": "tenant-123",
"isPublished": false,
"config": {
"columns": ["patient_name", "age", "last_visit"],
"groupBy": [],
"layout": "table"
}
}
Update View
PUT /api/views/{viewId}
Publish View
POST /api/views/{viewId}/publish
Request Body:
{
"tenantId": "tenant-123",
"userId": "user-456"
}
Change Tracking
Get Panel Changes
GET /api/changes/panels?tenantId={tenantId}&userId={userId}&since={timestamp}
Get View Notifications
GET /api/notifications/views?tenantId={tenantId}&userId={userId}&isRead=false
Using JavaScript/TypeScript
Basic Setup
const API_BASE_URL = 'http://localhost:3001'
// Helper function for API calls
async function apiCall(endpoint: string, options: RequestInit = {}) {
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
})
if (!response.ok) {
throw new Error(`API call failed: ${response.status} ${response.statusText}`)
}
return response.json()
}
Example Operations
Fetch All Panels
async function getAllPanels(tenantId: string, userId: string) {
try {
const panels = await apiCall(
`/api/panels?tenantId=${tenantId}&userId=${userId}`
)
console.log('Panels:', panels)
return panels
} catch (error) {
console.error('Failed to fetch panels:', error)
throw error
}
}
Create a New Panel
async function createPanel(
name: string,
description: string,
tenantId: string,
userId: string
) {
try {
const panel = await apiCall('/api/panels', {
method: 'POST',
body: JSON.stringify({
name,
description,
tenantId,
userId
})
})
console.log('Created panel:', panel)
return panel
} catch (error) {
console.error('Failed to create panel:', error)
throw error
}
}
Create a View
async function createView(
name: string,
panelId: number,
columns: string[],
tenantId: string,
userId: string
) {
try {
const view = await apiCall('/api/views', {
method: 'POST',
body: JSON.stringify({
name,
panelId,
config: {
columns,
groupBy: [],
layout: 'table'
},
tenantId,
userId
})
})
console.log('Created view:', view)
return view
} catch (error) {
console.error('Failed to create view:', error)
throw error
}
}
Using the Existing API Client
The codebase includes existing API client functions:
import { panelsAPI } from '@panels/app/api'
import { viewsAPI } from '@panels/app/api'
// List panels
const panels = await panelsAPI.all('tenant-123', 'user-456')
// Get specific panel
const panel = await panelsAPI.get({ id: 1 })
// Create panel
const newPanel = await panelsAPI.create({
name: 'Test Panel',
description: 'A test panel',
tenantId: 'tenant-123',
userId: 'user-456'
})
// List views
const views = await viewsAPI.all('tenant-123', 'user-456')
// Create view
const newView = await viewsAPI.create({
name: 'Test View',
panelId: 1,
config: {
columns: ['patient_name', 'age'],
groupBy: [],
layout: 'table'
},
tenantId: 'tenant-123',
userId: 'user-456'
})
Error Handling
Common HTTP Status Codes
- 200 OK: Request successful
- 201 Created: Resource created successfully
- 400 Bad Request: Invalid request data
- 401 Unauthorized: Authentication required
- 403 Forbidden: Access denied for tenant/user
- 404 Not Found: Resource not found
- 500 Internal Server Error: Server error
Example Error Response
{
"error": "Panel not found",
"statusCode": 404,
"message": "Panel with ID 999 not found for tenant tenant-123"
}
Error Handling in Code
async function safeApiCall(endpoint: string, options?: RequestInit) {
try {
return await apiCall(endpoint, options)
} catch (error) {
if (error instanceof Error) {
console.error('API Error:', error.message)
// Handle specific error cases
if (error.message.includes('404')) {
console.log('Resource not found')
} else if (error.message.includes('403')) {
console.log('Access denied')
} else if (error.message.includes('400')) {
console.log('Invalid request data')
}
}
throw error
}
}
Multi-Tenancy
All API calls require tenantId
and userId
parameters to ensure proper data isolation:
// Always include tenant and user context
const apiParams = {
tenantId: 'your-tenant-id',
userId: 'your-user-id'
}
// Include in query parameters for GET requests
const panels = await apiCall(
`/api/panels?tenantId=${apiParams.tenantId}&userId=${apiParams.userId}`
)
// Include in request body for POST/PUT requests
const panel = await apiCall('/api/panels', {
method: 'POST',
body: JSON.stringify({
name: 'Panel Name',
...apiParams
})
})
API Documentation
Swagger/OpenAPI
The API includes Swagger documentation available at:
http://localhost:3001/docs
This provides interactive documentation where you can test endpoints directly.
Best Practices
Request Validation
- All requests are validated using Zod schemas
- Include required fields:
tenantId
anduserId
- Use appropriate data types for each field
Performance
- The API uses FastifyJS for high performance
- Database operations use MikroORM for type safety
- Responses include only necessary data fields
Security
- Multi-tenant isolation at the API level
- All operations require tenant and user context
- Data access is restricted by tenant boundaries
Troubleshooting
Common Issues
Q: 404 errors when calling endpoints
- Verify the API server is running on the correct port
- Check that endpoints match the exact URL patterns
- Ensure you're using the correct HTTP method
Q: 403 Forbidden errors
- Verify tenantId and userId are correct
- Check that the user has access to the specified tenant
- Ensure you're including tenant context in all requests
Q: 400 Bad Request errors
- Check request body matches expected schema
- Verify all required fields are included
- Ensure data types match the API expectations
Q: Panel/View not found
- Confirm the resource exists for the specified tenant
- Check that IDs are correct and numeric where expected
- Verify user has access to the resource
Next Steps
- Panel entity reference - Understand the data model
- API client usage - Use the built-in client functions
- Frontend integration - Connect with React components
Related Topics
- Understanding multi-tenancy - Learn about tenant isolation
- Database schema - Explore the data structure
- Authentication setup - Set up user authentication