> ## 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.

# API Endpoints

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:

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  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")
  ```

  ```typescript TypeScript (Express) theme={null}
  import jwt from "jsonwebtoken";

  function authenticateUser(req, res, next) {
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith("Bearer ")) {
      return res.status(401).json({ error: "Missing authorization" });
    }

    const token = authHeader.replace("Bearer ", "");

    try {
      const payload = jwt.verify(token, process.env.SECRET_KEY);
      req.user = payload;
      next();
    } catch (error) {
      return res.status(401).json({ error: "Invalid token" });
    }
  }
  ```
</CodeGroup>

## Create Dashboard Endpoint

<Info>
  The `create_dashboard` function being called in the endpoint below should have already been created in
  the [Creating Dashboards](/documentation/multi-tenancy/creating-dashboards#creating-a-dashboard) guide.
</Info>

Create a new dashboard for a tenant.

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  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
      }
  ```

  ```typescript TypeScript (Express) theme={null}
  import express from "express";

  const router = express.Router();

  router.post("/dashboards", authenticateUser, async (req, res) => {
    const { tenantId, workspaceId, name, description } = req.body;

    // Verify user has permission
    if (!hasAccess(req.user, tenantId)) {
      return res.status(403).json({ error: "Access denied" });
    }

    try {
      const dashboard = await createDashboard(
        tenantId,
        workspaceId,
        name,
        description
      );

      res.json({
        success: true,
        data: dashboard,
      });
    } catch (error) {
      res.status(500).json({
        error: error.message,
      });
    }
  });
  ```

  ```php PHP (Laravel) theme={null}
  <?php
  Route::post('/dashboards', function (Request $request) {
      $validated = $request->validate([
          'tenant_id' => 'required|string',
          'workspace_id' => 'required|string',
          'name' => 'required|string',
          'description' => 'nullable|string',
      ]);

      // Verify user has permission
      if (!hasAccess(Auth::user(), $validated['tenant_id'])) {
          return response()->json(['error' => 'Access denied'], 403);
      }

      $dashboard = createDashboardForTenant(
          $validated['tenant_id'],
          $validated['workspace_id'],
          $validated['name'],
          $validated['description'] ?? null
      );

      return response()->json([
          'success' => true,
          'data' => $dashboard
      ]);
  })->middleware('auth:api');
  ?>
  ```
</CodeGroup>

**Request:**

```bash theme={null}
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:**

```json theme={null}
{
  "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.

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  @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
      }
  ```

  ```typescript TypeScript (Express) theme={null}
  router.get("/dashboards/:tenantId", authenticateUser, async (req, res) => {
    const { tenantId } = req.params;

    // Verify access
    if (!hasAccess(req.user, tenantId)) {
      return res.status(403).json({ error: "Access denied" });
    }

    const dashboard = await db.tenantDashboards.findOne({
      where: { tenant_id: tenantId },
    });

    if (!dashboard) {
      return res.status(404).json({ error: "Dashboard not found" });
    }

    res.json({
      success: true,
      data: dashboard,
    });
  });
  ```
</CodeGroup>

**Request:**

```bash theme={null}
GET /api/v1/dashboards/org-123
Authorization: Bearer YOUR_JWT_TOKEN
```

**Response:**

```json theme={null}
{
  "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.

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  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
      }
  ```

  ```typescript TypeScript (Express) theme={null}
  router.post("/dashboards/iframe-token", authenticateUser, async (req, res) => {
    const { tenantId, workspaceId, dashboardId, validUntil } = req.body;

    // Verify tenant belongs to user
    if (!hasAccess(req.user, tenantId)) {
      return res.status(403).json({ error: "Access denied" });
    }

    const tokenData = generateIframeToken(
      tenantId,
      workspaceId,
      dashboardId,
      validUntil
    );

    res.json({
      success: true,
      data: tokenData,
    });
  });
  ```
</CodeGroup>

**Request:**

```bash theme={null}
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:**

```json theme={null}
{
  "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.

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  @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)
      }
  ```

  ```typescript TypeScript (Express) theme={null}
  router.get("/dashboards", authenticateUser, async (req, res) => {
    const organizationId = req.user.organizationId;
    const { skip = 0, limit = 100 } = req.query;

    const dashboards = await db.tenantDashboards.findAll({
      where: { organization_id: organizationId },
      offset: parseInt(skip),
      limit: parseInt(limit),
    });

    res.json({
      success: true,
      data: dashboards,
      total: dashboards.length,
    });
  });
  ```
</CodeGroup>

## Delete Dashboard (Optional)

Delete a dashboard for a tenant.

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  @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"
      }
  ```

  ```typescript TypeScript (Express) theme={null}
  router.delete("/dashboards/:tenantId", authenticateUser, async (req, res) => {
    const { tenantId } = req.params;

    // Verify access
    if (!hasAccess(req.user, tenantId)) {
      return res.status(403).json({ error: "Access denied" });
    }

    const deleted = await db.tenantDashboards.destroy({
      where: { tenant_id: tenantId },
    });

    if (!deleted) {
      return res.status(404).json({ error: "Dashboard not found" });
    }

    res.json({
      success: true,
      message: "Dashboard deleted successfully",
    });
  });
  ```
</CodeGroup>

## Error Handling

Implement consistent error handling across all endpoints:

```json theme={null}
// 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:

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  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
  ```

  ```typescript TypeScript (Express) theme={null}
  import rateLimit from "express-rate-limit";

  const tokenLimiter = rateLimit({
    windowMs: 60 * 1000, // 1 minute
    max: 10, // 10 requests per minute
    message: "Too many token requests, please try again later",
  });

  router.post(
    "/dashboards/iframe-token",
    tokenLimiter,
    authenticateUser,
    async (req, res) => {
      // ... implementation
    }
  );
  ```
</CodeGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Security Best Practices" icon="shield-check" href="/documentation/multi-tenancy/security">
    Learn about security considerations for production
  </Card>

  <Card title="Frontend Setup" icon="react" href="/documentation/multi-tenancy/setup">
    Set up the frontend to embed dashboards
  </Card>
</CardGroup>
