Skip to main content

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

CodeStatusDescription
UNAUTHORIZED401Missing or invalid authentication
FORBIDDEN403User doesn’t have access to resource
NOT_FOUND404Resource not found
VALIDATION_ERROR422Invalid request parameters
SERVER_ERROR500Internal 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