Documentation Index
Fetch the complete documentation index at: https://docs.papermap.ai/llms.txt
Use this file to discover all available pages before exploring further.
This guide provides example REST API endpoints for dashboard operations in your backend.
Overview
Your API should expose endpoints for:
- Creating dashboards for tenants
- Retrieving tenant dashboards
- Generating iframe embed tokens
- Managing dashboard access
Authentication Middleware
All endpoints should require authentication. Here’s an example middleware:
from fastapi import Depends, HTTPException, Header
from jose import jwt, JWTError
async def get_authenticated_user(
authorization: str = Header(None)
) -> dict:
"""Verify JWT token and return user info"""
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing authorization")
token = authorization.replace("Bearer ", "")
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return payload
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
Create Dashboard Endpoint
The create_dashboard function being called in the endpoint below should have already been created in
the Creating Dashboards guide.
Create a new dashboard for a tenant.
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
router = APIRouter(prefix="/api/v1/dashboards", tags=["dashboards"])
class DashboardCreate(BaseModel):
tenant_id: str
workspace_id: str
name: str
description: Optional[str] = None
@router.post("/")
async def create_dashboard(
dashboard: DashboardCreate,
current_user: Dict = Depends(get_authenticated_user),
db: Session = Depends(get_db)
):
"""Create a dashboard for a tenant"""
# Verify user has permission to create dashboard for this tenant
if not has_access(current_user, dashboard.tenant_id):
raise HTTPException(status_code=403, detail="Access denied")
handler = DashboardHandler(db)
result = await handler.create_dashboard(
tenant_id=dashboard.tenant_id,
workspace_id=dashboard.workspace_id,
name=dashboard.name,
description=dashboard.description,
created_by=current_user["user_id"]
)
return {
"success": True,
"data": result
}
Request:
POST /api/v1/dashboards
Authorization: Bearer YOUR_JWT_TOKEN
{
"tenant_id": "org-123",
"workspace_id": "workspace-456",
"name": "Sales Dashboard",
"description": "Q4 Sales Analytics"
}
Response:
{
"success": true,
"data": {
"id": "dashboard-789",
"tenant_id": "org-123",
"workspace_id": "workspace-456",
"dashboard_id": "dashboard-789",
"created_at": "2024-01-15T10:30:00Z"
}
}
Get Dashboard by Tenant
Retrieve the dashboard for a specific tenant.
@router.get("/{tenant_id}")
def get_dashboard_by_tenant(
tenant_id: str,
current_user: Dict = Depends(get_authenticated_user),
db: Session = Depends(get_db)
):
"""Get dashboard for a specific tenant"""
# Verify access
if not has_access(current_user, tenant_id):
raise HTTPException(status_code=403, detail="Access denied")
handler = DashboardHandler(db)
dashboard = handler.get_dashboard_by_tenant(tenant_id)
if not dashboard:
raise HTTPException(status_code=404, detail="Dashboard not found")
return {
"success": True,
"data": dashboard
}
Request:
GET /api/v1/dashboards/org-123
Authorization: Bearer YOUR_JWT_TOKEN
Response:
{
"success": true,
"data": {
"id": "dashboard-789",
"tenant_id": "org-123",
"workspace_id": "workspace-456",
"dashboard_id": "dashboard-789",
"created_at": "2024-01-15T10:30:00Z"
}
}
Generate Iframe Token
Generate a secure token for embedding a dashboard.
class DashboardIframeTokenRequest(BaseModel):
tenant_id: str
workspace_id: str
dashboard_id: str
valid_until: Optional[int] = None
@router.post("/iframe-token")
def create_iframe_token(
request: DashboardIframeTokenRequest,
current_user: Dict = Depends(get_authenticated_user),
db: Session = Depends(get_db)
):
"""Generate secure token for embedding dashboard in iframe"""
# Verify tenant belongs to user
if not has_access(current_user, request.tenant_id):
raise HTTPException(status_code=403, detail="Access denied")
handler = DashboardHandler(db)
token_data = handler.generate_iframe_token(
tenant_id=request.tenant_id,
workspace_id=request.workspace_id,
dashboard_id=request.dashboard_id,
valid_until=request.valid_until
)
return {
"success": True,
"data": token_data
}
Request:
POST /api/v1/dashboards/iframe-token
Authorization: Bearer YOUR_JWT_TOKEN
{
"tenant_id": "org-123",
"workspace_id": "workspace-456",
"dashboard_id": "dashboard-789",
"valid_until": 1700000000 // Optional: Unix timestamp
}
Response:
{
"success": true,
"data": {
"token": "eyJhcGlfa2V5X2lkIjoieW91ci1hcGkta2V5IiwidGVuYW50X2lkIjoib3JnLTEyMyIsImRhc2hib2FyZF9pZCI6ImRhc2hib2FyZC03ODkiLCJ2YWxpZF91bnRpbCI6MTcwMDAwMDAwMCwic2lnbmF0dXJlIjoiYWJjMTIzLi4uIn0=",
"dashboard_id": "dashboard-789",
"expires_at": 1700000000
}
}
List All Dashboards (Optional)
List all dashboards for the authenticated user’s organization.
@router.get("/")
def list_dashboards(
current_user: Dict = Depends(get_authenticated_user),
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100
):
"""List all dashboards for user's organization"""
organization_id = current_user.get("organization_id")
handler = DashboardHandler(db)
dashboards = handler.list_dashboards(
organization_id=organization_id,
skip=skip,
limit=limit
)
return {
"success": True,
"data": dashboards,
"total": len(dashboards)
}
Delete Dashboard (Optional)
Delete a dashboard for a tenant.
@router.delete("/{tenant_id}")
async def delete_dashboard(
tenant_id: str,
current_user: Dict = Depends(get_authenticated_user),
db: Session = Depends(get_db)
):
"""Delete dashboard for a tenant"""
# Verify access
if not has_access(current_user, tenant_id):
raise HTTPException(status_code=403, detail="Access denied")
handler = DashboardHandler(db)
success = await handler.delete_dashboard(tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Dashboard not found")
return {
"success": True,
"message": "Dashboard deleted successfully"
}
Error Handling
Implement consistent error handling across all endpoints:
// Success Response
{
"success": true,
"data": { ... }
}
// Error Response
{
"success": false,
"error": "Error message here",
"code": "ERROR_CODE"
}
Common Error Codes
| Code | Status | Description |
|---|
UNAUTHORIZED | 401 | Missing or invalid authentication |
FORBIDDEN | 403 | User doesn’t have access to resource |
NOT_FOUND | 404 | Resource not found |
VALIDATION_ERROR | 422 | Invalid request parameters |
SERVER_ERROR | 500 | Internal server error |
Rate Limiting
Implement rate limiting to prevent abuse:
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@router.post("/iframe-token")
@limiter.limit("10/minute")
async def create_iframe_token(...):
# ... implementation
Next Steps
Security Best Practices
Learn about security considerations for production
Frontend Setup
Set up the frontend to embed dashboards