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

# Tenant Usage Tracking

Track per-tenant credit consumption and API usage to understand how each tenant utilizes your Papermap integration. This feature enables transparent billing, usage analytics, and resource monitoring.

## Overview

When using API key authentication with the `X-Tenant-ID` header, Papermap automatically tracks:

* **Credits consumed** per tenant per billing period
* **Request count** per tenant per billing period
* **Monthly aggregation** with automatic period bucketing

This allows workspace owners to see which tenants are consuming credits and resources.

## How It Works

```
API Request with X-Tenant-ID header
    ↓
Papermap processes request
    ↓
Credits consumed from workspace quota
    ↓
Usage attributed to tenant (monthly bucket)
    ↓
Query usage via API endpoints
```

### Billing Period

Usage is automatically bucketed by calendar month:

* **Period Start**: First day of month (e.g., 2025-01-01 00:00:00)
* **Period End**: Last day of month (e.g., 2025-01-31 23:59:59)

Each unique tenant gets a separate usage record per month.

## API Endpoints

<Info>
  The base URL for the Papermap API is `https://prod.dataapi.papermap.ai/`.
</Info>

### Get Workspace Tenant Usage

Retrieve usage data for all tenants in a workspace.

**Request:**

```bash theme={null}
GET /api/v1/external/workspaces/{workspace_id}/tenant-usage
Authorization: <HMAC Signature Headers>
```

**Query Parameters:**

| Parameter     | Type    | Required | Description                            |
| ------------- | ------- | -------- | -------------------------------------- |
| `from_period` | string  | No       | Start period filter (YYYY-MM format)   |
| `to_period`   | string  | No       | End period filter (YYYY-MM format)     |
| `page`        | integer | No       | Page number (default: 1)               |
| `per_page`    | integer | No       | Items per page (default: 50, max: 100) |

**Example Request:**

```bash theme={null}
curl -X GET "${PAPERMAP_API_URL}/api/v1/external/workspaces/ws-123/tenant-usage?from_period=2025-01&to_period=2025-12&page=1&per_page=20" \
  -H "X-API-Key-ID: your-api-key-id" \
  -H "X-Workspace-ID: ws-123" \
  -H "X-Valid-Until: 1735689600" \
  -H "X-Signature: <hmac-signature>"
```

**Response:**

```json theme={null}
{
  "success": true,
  "message": "Tenant usage retrieved successfully",
  "data": {
    "workspace_id": "ws-123",
    "usage": [
      {
        "tenant_id": "tenant-abc",
        "period": "2025-01",
        "period_start": "2025-01-01T00:00:00",
        "period_end": "2025-01-31T23:59:59",
        "credits_used": 1500,
        "request_count": 450
      },
      {
        "tenant_id": "tenant-xyz",
        "period": "2025-01",
        "period_start": "2025-01-01T00:00:00",
        "period_end": "2025-01-31T23:59:59",
        "credits_used": 800,
        "request_count": 200
      }
    ],
    "total_records": 2,
    "total_pages": 1
  }
}
```

### Get Tenant Usage History

Retrieve usage history for a specific tenant.

**Request:**

```bash theme={null}
GET /api/v1/external/workspaces/{workspace_id}/tenant-usage/{tenant_id}
Authorization: <HMAC Signature Headers>
```

**Query Parameters:**

| Parameter     | Type    | Required | Description                            |
| ------------- | ------- | -------- | -------------------------------------- |
| `from_period` | string  | No       | Start period filter (YYYY-MM format)   |
| `to_period`   | string  | No       | End period filter (YYYY-MM format)     |
| `page`        | integer | No       | Page number (default: 1)               |
| `per_page`    | integer | No       | Items per page (default: 50, max: 100) |

**Example Request:**

```bash theme={null}
curl -X GET "${PAPERMAP_API_URL}/api/v1/external/workspaces/ws-123/tenant-usage/tenant-abc?from_period=2024-06&to_period=2025-01" \
  -H "X-API-Key-ID: your-api-key-id" \
  -H "X-Workspace-ID: ws-123" \
  -H "X-Valid-Until: 1735689600" \
  -H "X-Signature: <hmac-signature>"
```

**Response:**

```json theme={null}
{
  "success": true,
  "message": "Tenant usage history retrieved successfully",
  "data": {
    "workspace_id": "ws-123",
    "tenant_id": "tenant-abc",
    "usage": [
      {
        "tenant_id": "tenant-abc",
        "period": "2024-12",
        "period_start": "2024-12-01T00:00:00",
        "period_end": "2024-12-31T23:59:59",
        "credits_used": 2100,
        "request_count": 630
      },
      {
        "tenant_id": "tenant-abc",
        "period": "2025-01",
        "period_start": "2025-01-01T00:00:00",
        "period_end": "2025-01-31T23:59:59",
        "credits_used": 1500,
        "request_count": 450
      }
    ],
    "total_records": 2,
    "total_pages": 1
  }
}
```

## Authentication

These endpoints use the same HMAC signature authentication as other external API endpoints. See the [Authentication Guide](/documentation/multi-tenancy/authentication) for details.

Required headers:

* `X-API-Key-ID`: Your API key identifier
* `X-Workspace-ID`: Your workspace ID
* `X-Valid-Until`: Unix timestamp for request expiration
* `X-Signature`: HMAC-SHA256 signature

## Implementation Examples

<Info>
  The `_get_auth_headers` function is a helper function that generates the HMAC signature for the API request found
  in the [HMAC Authentication](/documentation/multi-tenancy/authentication) guide.
</Info>

<CodeGroup>
  ```python Python theme={null}
  import httpx
  from typing import Optional

  async def get_tenant_usage(
      workspace_id: str,
      from_period: Optional[str] = None,
      to_period: Optional[str] = None,
      page: int = 1,
      per_page: int = 50
  ):
      headers = _get_auth_headers(workspace_id, API_KEY_ID, SECRET_KEY)

      params = {"page": page, "per_page": per_page}
      if from_period:
          params["from_period"] = from_period
      if to_period:
          params["to_period"] = to_period

      async with httpx.AsyncClient() as client:
          response = await client.get(
              f"{PAPERMAP_API_URL}/api/v1/external/workspaces/{workspace_id}/tenant-usage",
              headers=headers,
              params=params
          )

      return response.json()

  # Example usage
  usage = await get_tenant_usage("ws-123", from_period="2025-01", to_period="2025-12")
  for item in usage["data"]["usage"]:
      print(f"Tenant {item['tenant_id']}: {item['credits_used']} credits")
  ```

  ```typescript TypeScript/Node.js theme={null}
  import axios from "axios";

  interface TenantUsageItem {
    tenant_id: string;
    period: string;
    period_start: string;
    period_end: string;
    credits_used: number;
    request_count: number;
  }

  async function getTenantUsage(
    workspaceId: string,
    fromPeriod?: string,
    toPeriod?: string,
    page: number = 1,
    perPage: number = 50
  ): Promise<TenantUsageItem[]> {
    const service = new DashboardAPIService({
      apiKeyId: process.env.PAPERMAP_API_KEY_ID,
      secretKey: process.env.PAPERMAP_SECRET_KEY,
      workspaceId: workspaceId,
      baseUrl: process.env.PAPERMAP_API_URL,
    });

    const headers = service.getAuthHeaders();

    const params: Record<string, string> = {
      page: page.toString(),
      per_page: perPage.toString(),
    };
    if (fromPeriod) params.from_period = fromPeriod;
    if (toPeriod) params.to_period = toPeriod;

    const response = await axios.get(
      `${service.config.baseUrl}/api/v1/external/workspaces/${workspaceId}/tenant-usage`,
      { headers, params }
    );

    return response.data.data.usage;
  }

  // Example usage
  const usage = await getTenantUsage("ws-123", "2025-01", "2025-12");
  usage.forEach((item) => {
    console.log(`Tenant ${item.tenant_id}: ${item.credits_used} credits`);
  });
  ```

  ```php PHP theme={null}
  <?php
  function getTenantUsage(
      $workspaceId,
      $fromPeriod = null,
      $toPeriod = null,
      $page = 1,
      $perPage = 50
  ) {
      $headers = getAuthHeaders(
          $workspaceId,
          getenv('PAPERMAP_API_KEY_ID'),
          getenv('PAPERMAP_SECRET_KEY')
      );

      $params = ['page' => $page, 'per_page' => $perPage];
      if ($fromPeriod) $params['from_period'] = $fromPeriod;
      if ($toPeriod) $params['to_period'] = $toPeriod;

      $url = getenv('PAPERMAP_API_URL')
          . '/api/v1/external/workspaces/' . $workspaceId . '/tenant-usage?'
          . http_build_query($params);

      $ch = curl_init($url);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

      $headerArray = [];
      foreach ($headers as $key => $value) {
          $headerArray[] = "$key: $value";
      }
      curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArray);

      $response = curl_exec($ch);
      curl_close($ch);

      return json_decode($response, true);
  }

  // Example usage
  $usage = getTenantUsage('ws-123', '2025-01', '2025-12');
  foreach ($usage['data']['usage'] as $item) {
      echo "Tenant {$item['tenant_id']}: {$item['credits_used']} credits\n";
  }
  ?>
  ```

  ```java Java theme={null}
  public TenantUsageResponse getTenantUsage(
      String workspaceId,
      String fromPeriod,
      String toPeriod,
      int page,
      int perPage
  ) throws Exception {
      HttpHeaders headers = getAuthHeaders(workspaceId);

      UriComponentsBuilder builder = UriComponentsBuilder
          .fromHttpUrl(baseUrl + "/api/v1/external/workspaces/" + workspaceId + "/tenant-usage")
          .queryParam("page", page)
          .queryParam("per_page", perPage);

      if (fromPeriod != null) builder.queryParam("from_period", fromPeriod);
      if (toPeriod != null) builder.queryParam("to_period", toPeriod);

      HttpEntity<Void> request = new HttpEntity<>(headers);

      ResponseEntity<TenantUsageResponse> response = restTemplate.exchange(
          builder.toUriString(),
          HttpMethod.GET,
          request,
          TenantUsageResponse.class
      );

      return response.getBody();
  }
  ```

  ```csharp C# theme={null}
  public async Task<TenantUsageResponse> GetTenantUsage(
      string workspaceId,
      string fromPeriod = null,
      string toPeriod = null,
      int page = 1,
      int perPage = 50
  )
  {
      var headers = GetAuthHeaders(workspaceId);

      var queryParams = new List<string>
      {
          $"page={page}",
          $"per_page={perPage}"
      };
      if (!string.IsNullOrEmpty(fromPeriod))
          queryParams.Add($"from_period={fromPeriod}");
      if (!string.IsNullOrEmpty(toPeriod))
          queryParams.Add($"to_period={toPeriod}");

      var url = $"{_baseUrl}/api/v1/external/workspaces/{workspaceId}/tenant-usage?{string.Join("&", queryParams)}";

      var request = new HttpRequestMessage(HttpMethod.Get, url);
      foreach (var header in headers)
      {
          request.Headers.Add(header.Key, header.Value);
      }

      var response = await _httpClient.SendAsync(request);
      var content = await response.Content.ReadAsStringAsync();

      return JsonSerializer.Deserialize<TenantUsageResponse>(content);
  }
  ```

  ```go Go theme={null}
  func (s *DashboardAPIService) GetTenantUsage(
      workspaceID string,
      fromPeriod string,
      toPeriod string,
      page int,
      perPage int,
  ) (*TenantUsageResponse, error) {
      headers := s.getAuthHeaders(workspaceID)

      url := fmt.Sprintf(
          "%s/api/v1/external/workspaces/%s/tenant-usage?page=%d&per_page=%d",
          s.BaseURL, workspaceID, page, perPage,
      )
      if fromPeriod != "" {
          url += "&from_period=" + fromPeriod
      }
      if toPeriod != "" {
          url += "&to_period=" + toPeriod
      }

      req, _ := http.NewRequest("GET", url, nil)

      for key, value := range headers {
          req.Header.Set(key, value)
      }

      resp, err := s.Client.Do(req)
      if err != nil {
          return nil, err
      }
      defer resp.Body.Close()

      var result TenantUsageResponse
      json.NewDecoder(resp.Body).Decode(&result)

      return &result, nil
  }
  ```
</CodeGroup>

## Use Cases

<CardGroup cols={2}>
  <Card title="Billing & Invoicing" icon="file-invoice-dollar">
    Generate per-tenant invoices based on actual usage

    * Query usage for billing period
    * Calculate costs based on credits consumed
    * Automate invoice generation
  </Card>

  <Card title="Usage Analytics" icon="chart-line">
    Monitor tenant consumption patterns

    * Identify high-usage tenants
    * Track usage trends over time
    * Plan capacity accordingly
  </Card>

  <Card title="Quota Management" icon="gauge-high">
    Implement tenant-level quotas

    * Compare usage against limits
    * Send usage warnings
    * Enforce rate limits per tenant
  </Card>

  <Card title="Cost Allocation" icon="coins">
    Attribute costs to business units

    * Track departmental usage
    * Chargeback reporting
    * Budget planning
  </Card>
</CardGroup>

## Response Fields

| Field           | Type     | Description                                     |
| --------------- | -------- | ----------------------------------------------- |
| `tenant_id`     | string   | The tenant identifier from `X-Tenant-ID` header |
| `period`        | string   | Billing period in YYYY-MM format                |
| `period_start`  | datetime | Start of the billing period                     |
| `period_end`    | datetime | End of the billing period                       |
| `credits_used`  | integer  | Total credits consumed in this period           |
| `request_count` | integer  | Total API requests made in this period          |
| `total_records` | integer  | Total number of usage records (for pagination)  |
| `total_pages`   | integer  | Total number of pages available                 |

## Error Responses

| Status | Error                 | Description                          |
| ------ | --------------------- | ------------------------------------ |
| 400    | Invalid period format | Period must be in YYYY-MM format     |
| 401    | Unauthorized          | Invalid or missing authentication    |
| 403    | Access denied         | No access to the specified workspace |
| 404    | Not found             | Workspace or tenant not found        |

**Example Error Response:**

```json theme={null}
{
  "success": false,
  "message": "Invalid from_period format. Use YYYY-MM",
  "data": null
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/documentation/multi-tenancy/authentication">
    Learn how to authenticate API requests
  </Card>

  <Card title="API Endpoints" icon="plug" href="/documentation/multi-tenancy/api-endpoints">
    Explore other available API endpoints
  </Card>
</CardGroup>
