---
title: "Audit Logs"
description: "Dub maintains a multi-layered audit and telemetry architecture that captures administrative actions, partner modifications, workspace activities, and API or webhook requests across the platform. Ad..."
last_updated: "2026-10-12T07:00:10.087014+00:00"
canonical_url: "https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/analytics-pipeline/audit-logs"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [apps/web/app/ee/api/audit-logs/export/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/audit-logs/export/route.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/settings/security/audit-logs.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/security/audit-logs.tsx)
- [apps/web/app/ee/api/events/export/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/events/export/route.ts)
- [apps/web/app/ee/api/cron/export/events/workspace/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/export/events/workspace/route.ts)
- [apps/web/app/api/analytics/export/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/analytics/export/route.ts)
- [apps/web/lib/api/audit-logs/get-audit-logs.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/get-audit-logs.ts)
- [apps/web/lib/api/audit-logs/record-audit-log.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/record-audit-log.ts)
- [apps/web/app/api/logs/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/route.ts)
- [apps/web/lib/swr/use-activity-logs.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/swr/use-activity-logs.ts)
- [apps/web/app/api/activity-logs/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/activity-logs/route.ts)
- [apps/web/app/ee/api/partner-profile/programs/programId/activity-logs/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/activity-logs/route.ts)
- [apps/web/lib/api/audit-logs/schemas.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts)
- [apps/web/app/ee/api/events/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/events/route.ts)
- [apps/web/app/api/logs/logId/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/%5BlogId%5D/route.ts)
- [apps/web/app/ee/api/admin/events/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/admin/events/route.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/settings/logs/page.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/logs/page.tsx)
- [apps/web/app/api/logs/timeseries/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/timeseries/route.ts)
- [apps/web/lib/api/activity-log/track-activity-log.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-activity-log.ts)
- [apps/web/app/api/logs/count/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/count/route.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/settings/security/page-client.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/security/page-client.tsx)
- [apps/web/app/app.dub.co/dashboard/slug/ee/settings/webhooks/webhookId/page-client.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/webhooks/%5BwebhookId%5D/page-client.tsx)
- [apps/web/lib/api/activity-log/track-reward-overrides.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-reward-overrides.ts)
- [apps/web/app/api/workspaces/idOrSlug/import/importId/download/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/workspaces/%5BidOrSlug%5D/import/%5BimportId%5D/download/route.ts)
- [apps/web/lib/api/commissions/track-commission-update-activity-log.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/commissions/track-commission-update-activity-log.ts)
- [apps/web/lib/api-logs/capture-webhook-log.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api-logs/capture-webhook-log.ts)
- [apps/web/ui/activity-logs/activity-log-registry.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx)
- [apps/web/app/app.dub.co/dashboard/slug/ee/settings/logs/logId/page.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/logs/%5BlogId%5D/page.tsx)
- [apps/web/prisma/schema/activity.prisma](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/activity.prisma)
- [apps/web/app/api/webhooks/webhookId/events/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/webhooks/%5BwebhookId%5D/events/route.ts)
- [apps/web/lib/zod/schemas/activity-log.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/activity-log.ts)
</details>

## Overview

Dub maintains a multi-layered audit and telemetry architecture that captures administrative actions, partner modifications, workspace activities, and API or webhook requests across the platform. Administrative security events and audit records are ingested directly into [Tinybird Analytics](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/analytics-pipeline/tinybird-analytics) and ClickHouse [apps/web/lib/api/audit-logs/record-audit-log.ts:75-79](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/record-audit-log.ts#L75-L79), [apps/web/lib/api/audit-logs/get-audit-logs.ts:38-49](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/get-audit-logs.ts#L38-L49), whereas granular workspace operational changes—such as partner state transitions, reward overrides, and commission adjustments—are persisted relationally via Prisma [apps/web/prisma/schema/activity.prisma:1-16](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/activity.prisma#L1-L16), [apps/web/lib/api/activity-log/track-activity-log.ts:34-65](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-activity-log.ts#L34-L65). This infrastructure allows enterprises to query structured historical logs, enforce strict date range retention limits based on subscription plans [apps/web/app/api/logs/route.ts:13-21](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/route.ts#L13-L21), export audit datasets or event streams as downloadable CSVs [apps/web/app/ee/api/audit-logs/export/route.ts:40-47](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/audit-logs/export/route.ts#L40-L47), and render dedicated activity timelines directly inside the dashboard UI [apps/web/ui/activity-logs/activity-log-registry.tsx:71-129](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L71-L129).

Sources: [apps/web/lib/api/audit-logs/record-audit-log.ts:75-79](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/record-audit-log.ts#L75-L79), [apps/web/lib/api/audit-logs/get-audit-logs.ts:38-49](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/get-audit-logs.ts#L38-L49), [apps/web/prisma/schema/activity.prisma:1-16](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/activity.prisma#L1-L16), [apps/web/lib/api/activity-log/track-activity-log.ts:34-65](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-activity-log.ts#L34-L65), [apps/web/app/api/logs/route.ts:13-21](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/route.ts#L13-L21), [apps/web/app/ee/api/audit-logs/export/route.ts:40-47](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/audit-logs/export/route.ts#L40-L47), [apps/web/ui/activity-logs/activity-log-registry.tsx:71-129](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L71-L129)

## Tinybird Audit Event Ingestion

### Event Transformation and Enrichment

Administrative and security audit events across Dub are processed through a structured ingestion pipeline that shapes incoming records, resolves network context, and transmits payload telemetry to Tinybird. When `recordAuditLog` is invoked with single or multiple audit log inputs, the system resolves client IP addresses by inspecting request instances via `@vercel/functions` or falling back to workspace header helpers before transformation [apps/web/lib/api/audit-logs/record-audit-log.ts:47-56](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/record-audit-log.ts#L47-L56).

The `transformAuditLogTB` helper validates input schemas against `recordAuditLogInputSchema`, extracts the `user-agent` from incoming Next.js headers, and builds structured records [apps/web/lib/api/audit-logs/record-audit-log.ts:13-44](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/record-audit-log.ts#L13-L44). Unique identifiers are generated with an `audit_` prefix, workspace identifiers are normalized via `prefixWorkspaceId`, and complex structures such as `targets` and `metadata` are serialized into JSON strings [apps/web/lib/api/audit-logs/record-audit-log.ts:28-44](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/record-audit-log.ts#L28-L44). Batch or individual payloads are then submitted through `recordAuditLogTB`, which builds an ingest endpoint targeting the `dub_audit_logs` datasource with `wait: true` configuration [apps/web/lib/api/audit-logs/record-audit-log.ts:47-79](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/record-audit-log.ts#L47-L79).

> [!NOTE]
> If recording an audit log fails during transmission to Tinybird, the error is caught, logged to console alongside the serialized audit payloads, and dispatched to error tracking with user notification flags enabled via `@dub/utils` loggers [apps/web/lib/api/audit-logs/record-audit-log.ts:58-72](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/record-audit-log.ts#L58-L72).

Sources: [apps/web/lib/api/audit-logs/record-audit-log.ts:13-79](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/record-audit-log.ts#L13-L79)

### Action Types and Target Schemas

Audit events track specific domain operations across programs, rewards, discounts, partner enrollments, commissions, payouts, groups, bounties, and submissions. The accepted action identifiers are strictly constrained by validation schemas before reaching the persistence layer [apps/web/lib/api/audit-logs/schemas.ts:32-94](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L32-L94).

| Domain Category | Valid Action Identifiers | Sources |
| :--- | :--- | :--- |
| Program | `program.created`, `program.updated` | [apps/web/lib/api/audit-logs/schemas.ts:32-36](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L32-L36) |
| Rewards | `reward.created`, `reward.updated`, `reward.deleted` | [apps/web/lib/api/audit-logs/schemas.ts:37-41](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L37-L41) |
| Discounts | `discount.created`, `discount.updated`, `discount.deleted`, `discount_code.created`, `discount_code.deleted` | [apps/web/lib/api/audit-logs/schemas.ts:42-48](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L42-L48) |
| Partner Applications | `partner_application.approved`, `partner_application.rejected` | [apps/web/lib/api/audit-logs/schemas.ts:49-52](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L49-L52) |
| Partner Enrollments | `partner.created`, `partner.archived`, `partner.invited`, `partner.invite_deleted`, `partner.invite_resent`, `partner.enrollment_updated`, `partner.deactivated`, `partner.reactivated`, `partner.banned`, `partner.unbanned`, `partner.deleted` | [apps/web/lib/api/audit-logs/schemas.ts:53-65](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L53-L65) |
| Auto Approve Partner | `auto_approve_partner.enabled`, `auto_approve_partner.disabled` | [apps/web/lib/api/audit-logs/schemas.ts:66-69](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L66-L69) |
| Commissions & Clawbacks | `commission.created`, `commission.updated`, `clawback.created`, `commission.canceled`, `commission.marked_fraud`, `commission.marked_duplicate` | [apps/web/lib/api/audit-logs/schemas.ts:70-77](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L70-L77) |
| Payouts | `payout.confirmed`, `payout.marked_paid` | [apps/web/lib/api/audit-logs/schemas.ts:78-81](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L78-L81) |
| Groups | `group.created`, `group.updated`, `group.deleted` | [apps/web/lib/api/audit-logs/schemas.ts:82-86](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L82-L86) |
| Bounties | `bounty.created`, `bounty.updated`, `bounty.deleted`, `bounty_submission.approved`, `bounty_submission.rejected`, `bounty_submission.reopened` | [apps/web/lib/api/audit-logs/schemas.ts:87-94](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L87-L94) |

Sources: [apps/web/lib/api/audit-logs/schemas.ts:32-94](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/schemas.ts#L32-L94)

## Audit Log Retrieval and Export

### Querying Tinybird and ClickHouse

Audit records are retrieved by querying ClickHouse via Tinybird pipes using the `getAuditLogs` function. This utility executes the `get_audit_logs` pipe configured with strict input validation schemas and response serialization rules.

```typescript
export const getAuditLogs = async ({
  workspaceId,
  programId,
  start,
  end,
}: {
  start: Date;
  end: Date;
  workspaceId: string;
  programId: string;
}) => {
  const pipe = tb.buildPipe({
    pipe: "get_audit_logs",
    parameters: auditLogFilterSchemaTB,
    data: auditLogResponseSchemaTB,
  });

  const events = await pipe({
    workspaceId,
    programId,
    start: formatUTCDateTimeClickhouse(start),
    end: formatUTCDateTimeClickhouse(end),
  });

  return events.data;
```
Sources: [apps/web/lib/api/audit-logs/get-audit-logs.ts:27-51](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/get-audit-logs.ts#L27-L51)

The query parameters require a prefixed workspace identifier, program identifier, and formatted UTC date strings parsed through ClickHouse datetime formatting utilities.

```typescript
export const auditLogFilterSchemaTB = z.object({
  workspaceId: z.string().transform(prefixWorkspaceId),
  programId: z.string(),
  start: z.string(),
  end: z.string(),
});

export const auditLogResponseSchemaTB = z.object({
  id: z.string(),
  timestamp: z.string(),
  action: z.string(),
  actor_id: z.string(),
  actor_type: z.string(),
  actor_name: z.string(),
  description: z.string(),
  ip_address: z.string(),
  user_agent: z.string(),
  targets: z.string(),
  metadata: z.string(),
});
```
Sources: [apps/web/lib/api/audit-logs/get-audit-logs.ts:6-25](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/audit-logs/get-audit-logs.ts#L6-L25)

### API Export Endpoint and Plan Enforcement

The backend export workflow is handled by a POST route secured via `withWorkspace`. The route requires an enterprise plan capability and ownership or member roles before processing requests.

```typescript
export const POST = withWorkspace(
  async ({ req, workspace }) => {
    const { start, end } = auditLogExportQuerySchema.parse(
      await parseRequestBody(req),
    );

    if (!start || !end) {
      throw new DubApiError({
        code: "bad_request",
        message: "Must provide start and end dates.",
      });
    }

    const { canExportAuditLogs } = getPlanCapabilities(workspace.plan);

    if (!canExportAuditLogs) {
      throw new DubApiError({
        code: "forbidden",
        message: "You are not authorized to export audit logs.",
      });
    }

    const programId = getDefaultProgramIdOrThrow(workspace);

    const auditLogs = await getAuditLogs({
      workspaceId: workspace.id,
      programId,
      start: new Date(start),
      end: new Date(end),
    });

    const csvData = convertToCSV(auditLogs);

    return new Response(csvData, {
      headers: {
        "Content-Type": "application/csv",
        "Content-Disposition": `attachment;`,
      },
    });
  },
  {
    requiredRoles: ["owner", "member"],
    requiredPlan: ["enterprise"],
  },
);
```
Sources: [apps/web/app/ee/api/audit-logs/export/route.ts:16-60](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/audit-logs/export/route.ts#L16-L60)

> [!WARNING]
> Requests lacking `start` and `end` query parameters or originating from workspaces without enterprise plan capabilities throw explicit `DubApiError` exceptions with `bad_request` or `forbidden` error codes.

Sources: [apps/web/app/ee/api/audit-logs/export/route.ts:22-36](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/audit-logs/export/route.ts#L22-L36)

### Frontend Client and CSV Generation

The client interface component manages date interval selections using `SimpleDateRangePicker` and triggers file downloads by converting retrieved audit logs into CSV payloads.

Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/settings/security/audit-logs.tsx:22-66](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/security/audit-logs.tsx#L22-L66)

When export permissions are absent on lower-tier workspace plans, the interface disables the date picker and export button, presenting an upgrade callout prompting users to transition to the Enterprise Plan.

Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/settings/security/audit-logs.tsx:106-130](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/settings/security/audit-logs.tsx#L106-L130)

## Workspace Activity Log Persistence

### Database Schema and Prisma Modeling

Activity logs within [partner programs](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/partner-programs/partner-programs) are modeled using the `ActivityLog` Prisma model inside the database schema. Each record captures identifiers for the workspace, program, parent resource, and specific target resource, along with action metadata, descriptive text, optional batching tokens, and JSON-encoded change sets.

```
model ActivityLog {
  id                 String   @id @default(cuid())
  workspaceId        String
  programId          String
  parentResourceType String?
  parentResourceId   String?
  resourceType       String
  resourceId         String
  userId             String?
  action             String
  description        String?  @db.Text
  batchId            String?
  changeSet          Json?
  createdAt          DateTime @default(now())

```
Sources: [apps/web/prisma/schema/activity.prisma:1-15](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/activity.prisma#L1-L15)

The table references user entities via a foreign key relation on `userId` and maintains performance indexes on `[resourceType, resourceId]` and `userId`.

```
  user User? @relation(fields: [userId], references: [id])

  @@index([resourceType, resourceId])
  @@index(userId)
}
```
Sources: [apps/web/prisma/schema/activity.prisma:16-20](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/activity.prisma#L16-L20)

### Activity Tracking and Filtering Logic

The programmatic insertion of activity logs is managed by `trackActivityLog` and transaction-bound `trackActivityLogsTx` utilities. These functions filter incoming entries to ensure that actions either require no change set or contain a non-empty change object before executing bulk creations.

```typescript
const ACTIONS_WITHOUT_CHANGE_SET: ActivityLogAction[] = [
  "submittedLead.created",
  "reward.created",
  "reward.deleted",
];

export interface TrackActivityLogInput
  extends Pick<
    Prisma.ActivityLogUncheckedCreateInput,
    | "workspaceId"
    | "programId"
    | "resourceId"
    | "userId"
    | "description"
    | "parentResourceType"
    | "parentResourceId"
    | "batchId"
  > {
  resourceType: ActivityLogResourceType;
  action: ActivityLogAction;
  changeSet?: ChangeSet;
}

export const trackActivityLog = async (
  input: TrackActivityLogInput | TrackActivityLogInput[],
) => {
  let inputs = Array.isArray(input) ? input : [input];

  inputs = inputs.filter(
    (i) =>
      ACTIONS_WITHOUT_CHANGE_SET.includes(i.action) ||
      (i.changeSet && Object.keys(i.changeSet).length > 0),
  );

  if (inputs.length === 0) {
    return;
  }
```
Sources: [apps/web/lib/api/activity-log/track-activity-log.ts:11-47](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-activity-log.ts#L11-L47)

```typescript
export const trackActivityLogsTx = async ({
  tx,
  logs,
}: {
  tx: Prisma.TransactionClient;
  logs: TrackActivityLogInput | TrackActivityLogInput[];
}) => {
  let inputs = Array.isArray(logs) ? logs : [logs];

  inputs = inputs.filter(
    (i) =>
      ACTIONS_WITHOUT_CHANGE_SET.includes(i.action) ||
      (i.changeSet && Object.keys(i.changeSet).length > 0),
  );

  if (inputs.length === 0) {
    return;
  }
```
Sources: [apps/web/lib/api/activity-log/track-activity-log.ts:68-85](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-activity-log.ts#L68-L85)

Actions configured to bypass the change set requirement include submission creations and reward creations or deletions.

| Action Name | Description / Behavior | Sources |
| :--- | :--- | :--- |
| `submittedLead.created` | Bypasses change set validation during lead submission creation | [apps/web/lib/api/activity-log/track-activity-log.ts:11-15](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-activity-log.ts#L11-L15) |
| `reward.created` | Bypasses change set validation when a new reward entity is instantiated | [apps/web/lib/api/activity-log/track-activity-log.ts:11-15](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-activity-log.ts#L11-L15) |
| `reward.deleted` | Bypasses change set validation upon reward removal | [apps/web/lib/api/activity-log/track-activity-log.ts:11-15](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-activity-log.ts#L11-L15) |

Sources: [apps/web/lib/api/activity-log/track-activity-log.ts:11-15](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-activity-log.ts#L11-L15)

> [!NOTE]
> If an input array contains entries where actions lack the explicit exemption and have an empty or missing `changeSet`, those entries are filtered out and omitted from database writes entirely.

Sources: [apps/web/lib/api/activity-log/track-activity-log.ts:39-43](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-activity-log.ts#L39-L43)

### Reward and Commission Activity Trackers

Partner and link-level reward overrides fetch previous and subsequent reward and discount records to construct serialized activity snapshots. The tracking functions verify that changes have occurred across click, lead, or sale reward identifiers or discount configurations before persisting log entries.

```typescript
const rewardFields = [
  ["clickReward", previous.clickRewardId, next.clickRewardId],
  ["leadReward", previous.leadRewardId, next.leadRewardId],
  ["saleReward", previous.saleRewardId, next.saleRewardId],
] as const;

const changedRewardFields = rewardFields.filter(
  ([, oldId, newId]) => oldId !== newId,
);
const discountChanged = previous.discountId !== next.discountId;

if (changedRewardFields.length === 0 && !discountChanged) {
  return;
}
```
Sources: [apps/web/lib/api/activity-log/track-reward-overrides.ts:44-57](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/activity-log/track-reward-overrides.ts#L44-L57)

Commission updates monitor modifications specifically across amount, earnings, and status fields using resource diff utilities. Bulk status updates group commissions by program to delegate consistent status changes efficiently.

```typescript
const COMMISSION_ACTIVITY_FIELDS = ["amount", "earnings", "status"];

export function toCommissionActivitySnapshot(
  commission: Pick<Commission, "id" | "amount" | "earnings" | "status">,
): CommissionActivitySnapshot {
  return {
    amount: commission.amount,
    earnings: commission.earnings,
    status: commission.status,
  };
}
```
Sources: [apps/web/lib/api/commissions/track-commission-update-activity-log.ts:19-29](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/commissions/track-commission-update-activity-log.ts#L19-L29)

```typescript
export async function trackCommissionStatusUpdatesByProgram({
  commissions,
  payouts,
  newStatus = "paid",
}: {
  commissions: Pick<
    Commission,
    "id" | "amount" | "earnings" | "status" | "programId"
  >[];
  payouts: Array<{ program: { id: string; workspaceId: string } }>;
  newStatus?: CommissionStatus;
}) {
  if (commissions.length === 0) {
    return;
  }

  const workspaceByProgram = new Map(
    payouts.map((p) => [p.program.id, p.program.workspaceId]),
  );

  const commissionsByProgram = groupBy(commissions, (c) => c.programId);

```
Sources: [apps/web/lib/api/commissions/track-commission-update-activity-log.ts:103-124](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/commissions/track-commission-update-activity-log.ts#L103-L124)

## Activity Log Querying and UI Rendering

### Workspace and Partner API Endpoints

Activity log retrieval is divided between internal workspace management routes and partner-facing profile endpoints. The workspace route `/api/activity-logs` is protected by `withWorkspace` middleware, validating query parameters via `getActivityLogsQuerySchema` and fetching up to 100 entries ordered by creation date descending with user relations included.

```typescript
export const GET = withWorkspace(async ({ workspace, searchParams }) => {
  const { resourceType, resourceId, parentResourceId, action } =
    getActivityLogsQuerySchema.parse(searchParams);

  const programId = getDefaultProgramIdOrThrow(workspace);

  const activityLogs = await prisma.activityLog.findMany({
    where: {
      programId,
      resourceType,
      resourceId,
      parentResourceId,
      action,
    },
    orderBy: {
      createdAt: "desc",
    },
    take: 100,
    include: {
      user: true,
    },
  });
```
Sources: [apps/web/app/api/activity-logs/route.ts:13-34](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/activity-logs/route.ts#L13-L34)

When querying partner resources, the workspace endpoint automatically polyfills a initial `partner.groupChanged` activity log if a corresponding program enrollment exists, referencing the enrollment creation date and group details.

```typescript
if (resourceType === "partner" && resourceId) {
  const programEnrollment = await prisma.programEnrollment.findUnique({
    where: {
      partnerId_programId: {
        partnerId: resourceId,
        programId,
      },
    },
    select: {
      createdAt: true,
      groupId: true,
      partnerGroup: {
        select: {
          name: true,
        },
      },
    },
  });
  if (programEnrollment) {
    parsedActivityLogs.push({
      id: uuid(),
      action: "partner.groupChanged",
      description: null,
      createdAt: programEnrollment.createdAt,
      user: null,
      changeSet: {
        group: {
          old: null,
          new: parsedActivityLogs[parsedActivityLogs.length - 1]?.changeSet
            ?.group?.old ?? {
            id: programEnrollment.groupId,
            name: programEnrollment.partnerGroup?.name,
          },
        },
      },
    });
  }
```
Sources: [apps/web/app/api/activity-logs/route.ts:39-75](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/activity-logs/route.ts#L39-L75)

Conversely, the partner profile endpoint at `/(ee)/api/partner-profile/programs/[programId]/activity-logs/route.ts` is secured with `withPartnerProfile` and restricts querying strictly to the `submittedLead` resource type. It validates that the requested lead identifier belongs to the authenticated partner and program, and explicitly strips internal user identities (`user: null`) from the serialized response payload.

```typescript
export const GET = withPartnerProfile(
  async ({ partner, params, searchParams }) => {
    const { resourceType, resourceId, action } =
      getActivityLogsQuerySchema.parse(searchParams);

    // Limit to submitted lead for now
    if (resourceType !== "submittedLead") {
      throw new DubApiError({
        code: "bad_request",
        message: "Resource type must be submitted lead.",
      });
    }

    const programEnrollment = await getProgramEnrollmentOrThrow({
      partnerId: partner.id,
      programId: params.programId,
      include: {},
    });

    // Check if the resource is a referral and belongs to the program and partner
    const lead = await prisma.submittedLead.findUnique({
      where: {
        id: resourceId,
        programId: programEnrollment.programId,
        partnerId: partner.id,
      },
      select: {
        id: true,
      },
    });

    if (!lead) {
      throw new DubApiError({
        code: "not_found",
        message: "Submitted lead not found.",
      });
    }

    const activityLogs = await prisma.activityLog.findMany({
      where: {
        programId: programEnrollment.programId,
        resourceType,
        resourceId,
        action,
      },
      orderBy: {
        createdAt: "desc",
      },
      take: 100,
    });

    // Hide the workspace user who made each change from the partner
    return NextResponse.json(
      z
        .array(activityLogSchema)
        .parse(activityLogs.map((log) => ({ ...log, user: null }))),
    );
```
Sources: [apps/web/app/ee/api/partner-profile/programs/programId/activity-logs/route.ts:12-68](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/activity-logs/route.ts#L12-L68)

> [!WARNING]
> Requests to the partner profile activity logs endpoint with any `resourceType` other than `submittedLead` throw an immediate `DubApiError` with a `bad_request` status code.

Sources: [apps/web/app/ee/api/partner-profile/programs/programId/activity-logs/route.ts:17-23](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/activity-logs/route.ts#L17-L23)

### Client SWR Hook and Request Guardrails

The client-side `useActivityLogs` hook wraps SWR to fetch activity logs for a specific resource within the current workspace context retrieved from `useWorkspace`.

```typescript
export function useActivityLogs({
  query,
  enabled = true,
}: {
  query?: GetActivityLogsQuery;
  enabled?: boolean;
} = {}) {
  const { id: workspaceId } = useWorkspace();

  const searchParams = query
    ? new URLSearchParams({
        workspaceId: workspaceId!,
        ...query,
      }).toString()
    : "";

  const requestEnabled =
    enabled &&
    workspaceId &&
    query?.resourceType &&
    (query?.parentResourceId || query?.resourceId);

  const { data, error, isLoading, mutate } = useSWR<ActivityLog[]>(
    requestEnabled && `/api/activity-logs?${searchParams}`,
    fetcher,
    {
      keepPreviousData: true,
    },
  );

  return {
    activityLogs: data,
    error,
    loading: isLoading,
    mutate,
  };
```
Sources: [apps/web/lib/swr/use-activity-logs.ts:6-41](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/swr/use-activity-logs.ts#L6-L41)

The hook evaluates an explicit `requestEnabled` boolean to determine whether SWR should execute the network request.

```typescript
const requestEnabled =
  enabled &&
  workspaceId &&
  query?.resourceType &&
  (query?.parentResourceId || query?.resourceId);

```
Sources: [apps/web/lib/swr/use-activity-logs.ts:22-27](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/swr/use-activity-logs.ts#L22-L27)

| Condition Variable | Type / Requirement | Description | Sources |
| :--- | :--- | :--- | :--- |
| `enabled` | boolean (default: `true`) | Optional toggle flag passed to the hook argument | [apps/web/lib/swr/use-activity-logs.ts:8-11](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/swr/use-activity-logs.ts#L8-L11) |
| `workspaceId` | string | Active workspace identifier resolved from `useWorkspace()` | [apps/web/lib/swr/use-activity-logs.ts:13-24](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/swr/use-activity-logs.ts#L13-L24) |
| `resourceType` | string | Mandatory property inside the `query` object | [apps/web/lib/swr/use-activity-logs.ts:25-25](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/swr/use-activity-logs.ts#L25-L25) |
| `parentResourceId` \| `resourceId` | string | At least one resource identifier must be provided in the query | [apps/web/lib/swr/use-activity-logs.ts:26-26](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/swr/use-activity-logs.ts#L26-L26) |

Sources: [apps/web/lib/swr/use-activity-logs.ts:8-27](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/swr/use-activity-logs.ts#L8-L27)

### UI Registry and Action Renderers

The activity log UI layer maps distinct action strings to specialized React components and UI icons using registries defined in `activity-log-registry.tsx`. The actor type is classified via `getActorType`, returning `"USER"` if a user relation is present or `"SYSTEM"` otherwise.

```tsx
export type ActorType = "USER" | "SYSTEM";

type ActivityLogRenderer = (props: { log: ActivityLog }) => ReactNode;

export function getActorType(log: ActivityLog): ActorType {
  return log.user ? "USER" : "SYSTEM";
}
```
Sources: [apps/web/ui/activity-logs/activity-log-registry.tsx:35-41](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L35-L41)

Partner status actions encompass a fixed set of lifecycle events mapped to status change renderers and dotted circle icons.

```tsx
const PARTNER_STATUS_ACTIONS = [
  "partner_application.approved",
  "partner_application.rejected",
  "partner.banned",
  "partner.unbanned",
  "partner.deactivated",
  "partner.reactivated",
  "partner.archived",
  "partner.unarchived",
] as const satisfies readonly ActivityLogAction[];
```
Sources: [apps/web/ui/activity-logs/activity-log-registry.tsx:24-33](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L24-L33)

| Activity Action Constant | Icon Component | Renderer Component | Sources |
| :--- | :--- | :--- | :--- |
| `partner.groupChanged` | `UserArrowRight` | `PartnerGroupChangedRenderer` | [apps/web/ui/activity-logs/activity-log-registry.tsx:46-78](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L46-L78) |
| `partner.rewardChanged` | `MoneyBill2` | `PartnerRewardChangedRenderer` | [apps/web/ui/activity-logs/activity-log-registry.tsx:47-82](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L47-L82) |
| `partner.discountChanged` | `DiscountCode` | `PartnerDiscountChangedRenderer` | [apps/web/ui/activity-logs/activity-log-registry.tsx:48-86](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L48-L86) |
| `link.rewardChanged` | `MoneyBill2` | `PartnerRewardChangedRenderer` | [apps/web/ui/activity-logs/activity-log-registry.tsx:49-90](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L49-L90) |
| `link.discountChanged` | `DiscountCode` | `PartnerDiscountChangedRenderer` | [apps/web/ui/activity-logs/activity-log-registry.tsx:50-94](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L50-L94) |
| Partner Status Actions (8 types) | `CircleDotted` | `PartnerStatusChangedRenderer` | [apps/web/ui/activity-logs/activity-log-registry.tsx:24-98](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L24-L98) |
| `submittedLead.created` | `FileSend` | `SubmittedLeadCreatedRenderer` | [apps/web/ui/activity-logs/activity-log-registry.tsx:55-102](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L55-L102) |
| Submitted Lead Status Actions (6 types) | `UserClock` | `SubmittedLeadStatusChangedRenderer` | [apps/web/ui/activity-logs/activity-log-registry.tsx:56-115](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L56-L115) |
| Reward Actions (6 types) | `MoneyBill2` / `Pen2` / `CircleMinus` / `CirclePlusIcon` | `RewardActivityRenderer` | [apps/web/ui/activity-logs/activity-log-registry.tsx:63-128](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L63-L128) |

Sources: [apps/web/ui/activity-logs/activity-log-registry.tsx:24-128](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L24-L128)

> [!TIP]
> If an activity log action lacks a registered icon mapping in `ACTIVITY_LOG_ICONS`, `getActivityLogIcon` automatically falls back to rendering a neutral informational icon (`CircleInfo`).

Sources: [apps/web/ui/activity-logs/activity-log-registry.tsx:141-149](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/activity-logs/activity-log-registry.tsx#L141-L149)

## API Request and Webhook Logging

### Request Capture and Endpoints

Incoming API requests and webhooks are recorded via logging utilities and exposed through workspace endpoints. The webhook capture utility parses request and response payloads, mapping incoming paths to specific actors using `WEBHOOK_REQUEST_ACTORS_BY_PATH` before recording logs with `recordApiLog`.

```typescript
export async function captureWebhookLog({
  workspaceId,
  method,
  path,
  statusCode,
  duration,
  requestBody,
  responseBody,
  userAgent,
}: {
  workspaceId: string;
  method: string;
  path: keyof typeof WEBHOOK_REQUEST_ACTORS_BY_PATH;
  statusCode: number;
  duration: number;
  requestBody: unknown;
  responseBody: unknown;
  userAgent: string | null;
}) {
  const actor = WEBHOOK_REQUEST_ACTORS_BY_PATH[path];

  return await recordApiLog({
    workspaceId,
    method,
    path,
    routePattern: path,
    statusCode,
    duration,
    userAgent,
    requestBody,
    responseBody: await parseResponseBody(responseBody),
    tokenId: null,
    userId: actor.id,
    requestType: "webhook",
  });
}
```
Sources: [apps/web/lib/api-logs/capture-webhook-log.ts:16-51](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api-logs/capture-webhook-log.ts#L16-L51)

Workspace API routes process query parameters, enforce plan retention rules, and return JSON responses for request logs, counts, and timeseries aggregations.

| Endpoint Route | HTTP Method | Required Permissions | Required Plan | Sources |
| :--- | :--- | :--- | :--- | :--- |
| `/api/logs` | `GET` | `workspaces.read` | Any | [apps/web/app/api/logs/route.ts:9-40](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/route.ts#L9-L40) |
| `/api/logs/:logId` | `GET` | `workspaces.read` | Any | [apps/web/app/api/logs/logId/route.ts:9-44](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/%5BlogId%5D/route.ts#L9-L44) |
| `/api/logs/timeseries` | `GET` | `workspaces.read` | Any | [apps/web/app/api/logs/timeseries/route.ts:8-36](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/timeseries/route.ts#L8-L36) |
| `/api/logs/count` | `GET` | `workspaces.read` | Any | [apps/web/app/api/logs/count/route.ts:8-34](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/count/route.ts#L8-L34) |
| `/api/webhooks/[webhookId]/events` | `GET` | `webhooks.read` | `business`, `advanced`, `enterprise` | [apps/web/app/api/webhooks/webhookId/events/route.ts:7-33](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/webhooks/%5BwebhookId%5D/events/route.ts#L7-L33) |

Sources: [apps/web/app/api/logs/route.ts:9-40](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/route.ts#L9-L40), [apps/web/app/api/logs/logId/route.ts:9-44](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/%5BlogId%5D/route.ts#L9-L44), [apps/web/app/api/logs/timeseries/route.ts:8-36](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/timeseries/route.ts#L8-L36), [apps/web/app/api/logs/count/route.ts:8-34](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/logs/count/route.ts#L8-L34), [apps/web/app/api/webhooks/webhookId/events/route.ts:7-33](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/webhooks/%5BwebhookId%5D/events/route.ts#L7-L33)

## Related

- [Admin Dashboard](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/administration-and-api/admin-dashboard)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/llms.txt) for all pages in this wiki.
