---
title: "Data Exports"
description: "Data Exports enables workspaces and partners to extract large volumes of operational metrics, analytics, and partnership data into structured formats like CSV spreadsheets and ZIP archives. By offe..."
last_updated: "2026-10-05T05:07:35.222511+00:00"
canonical_url: "https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/analytics-and-tracking/data-exports"
---

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

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

- [apps/web/app/ee/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts)
- [apps/web/app/ee/api/cron/export/payouts/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/payouts/route.ts)
- [apps/web/app/ee/api/cron/export/events/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/partner/route.ts)
- [apps/web/app/api/analytics/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/analytics/export/route.ts)
- [apps/web/app/ee/api/cron/export/customers/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/partner/route.ts)
- [apps/web/app/ee/api/commissions/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/commissions/export/route.ts)
- [apps/web/app/ee/api/cron/export/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/links/route.ts)
- [apps/web/app/ee/api/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/events/export/route.ts)
- [apps/web/app/ee/api/partner-profile/programs/programId/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/events/export/route.ts)
- [apps/web/app/ee/api/partner-profile/programs/programId/analytics/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/analytics/export/route.ts)
- [apps/web/app/ee/api/payouts/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/payouts/export/route.ts)
- [apps/web/app/ee/api/cron/export/customers/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/route.ts)
- [apps/web/app/ee/api/cron/export/partners/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/partners/route.ts)
- [apps/web/app/ee/api/cron/export/events/workspace/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/workspace/route.ts)
- [apps/web/app/ee/api/partner-profile/programs/programId/customers/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/customers/export/route.ts)
- [apps/web/app/api/links/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/links/export/route.ts)
- [apps/web/app/ee/api/cron/framer/backfill-leads-batch/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/framer/backfill-leads-batch/route.ts)
- [apps/web/app/ee/api/customers/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/customers/export/route.ts)
- [apps/web/app/ee/api/cron/import/tapfiliate/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/import/tapfiliate/route.ts)
- [apps/web/app/ee/api/audit-logs/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/audit-logs/export/route.ts)
- [apps/web/app/ee/api/partners/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partners/export/route.ts)
- [apps/web/app/ee/api/cron/import/rewardful/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/import/rewardful/route.ts)
- [apps/web/app/ee/api/cron/import/lemonsqueezy/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/import/lemonsqueezy/route.ts)
- [apps/web/app/ee/api/cron/aggregate-clicks/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/aggregate-clicks/route.ts)
- [apps/web/scripts/programs/5-import-customer-sales.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/scripts/programs/5-import-customer-sales.ts)
- [apps/web/lib/analytics/export-analytics-to-zip.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/analytics/export-analytics-to-zip.ts)
- [apps/web/app/ee/api/program-applications/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/program-applications/export/route.ts)
- [apps/web/app/ee/api/cron/streams/update-click-stats/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/streams/update-click-stats/route.ts)
- [apps/web/app/ee/api/admin/payouts/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/admin/payouts/route.ts)
- [apps/web/ui/analytics/analytics-export-button.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/analytics/analytics-export-button.tsx)
</details>

## Overview

Data Exports enables workspaces and partners to extract large volumes of operational metrics, analytics, and partnership data into structured formats like CSV spreadsheets and ZIP archives. By offering both immediate synchronous responses for small datasets and QStash-driven asynchronous background pipelines for large workloads, the system prevents request timeouts while securely delivering generated artifacts via signed storage URLs and email notifications. Sources: [apps/web/app/(ee)/api/commissions/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/commissions/export/route.ts#L12-L41), [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L83-L101), [apps/web/app/api/analytics/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/analytics/export/route.ts#L109-L128)

## Synchronous Versus Background Export Architecture

### Synchronous Versus Background Export Architecture

### Overview

The export system balances request responsiveness with computational safety by routing requests through either a direct synchronous stream or an asynchronous QStash worker pipeline. When client requests query small datasets or request compressed ZIP archives like analytics exports, endpoints execute within standard HTTP timeouts and return binaries directly. When query thresholds exceed predefined volume limits—such as over 1,000 links or commissions—endpoints offload processing to background QStash workers, returning an immediate `202 Accepted` status.

Sources: [apps/web/app/(ee)/api/commissions/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/commissions/export/route.ts#L12-L41), [apps/web/app/api/analytics/export/route.ts:21-128](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/analytics/export/route.ts#L21-L128), [apps/web/app/api/links/export/route.ts:19-60](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/links/export/route.ts#L19-L60)

### Execution Pathway Comparison

The execution path diverges based on data volume checks executed prior to export generation. For links and commissions, endpoints query row counts using `getLinksCount` and `getCommissionsCount`, comparing totals against `MAX_LINKS_TO_EXPORT` (1,000) and `MAX_COMMISSIONS_TO_EXPORT` (1,000).

```mermaid
graph TD
    A[Client Request] --> B{Count > Threshold?}
    B -- Yes --> C[Publish QStash Job]
    C --> D[Return 202 Accepted]
    B -- No --> E[Fetch Batch / Stream CSV]
    E --> F[Return Direct Response]
```

Sources: [apps/web/app/(ee)/api/commissions/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/commissions/export/route.ts#L23-L62), [apps/web/app/api/links/export/route.ts:21-92](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/links/export/route.ts#L21-L92)

### Architectural Trade-Offs

| Approach | Benefit | Cost |
| :--- | :--- | :--- |
| Synchronous CSV / ZIP (`GET`) | Immediate file download in browser; simpler request-response lifecycle without external queues. | Subject to Vercel/HTTP timeout limits (e.g., `maxDuration = 300` for analytics); risks memory exhaustion on large result sets. |
| Asynchronous QStash Worker (`POST`) | Handles unbounded datasets via chunked batch iteration; offloads heavy CPU/IO processing from HTTP request threads. | Requires webhook signature verification (`verifyQstashSignature`), storage upload management (`createDownloadableExport`), and email dispatch (`sendEmail`). |

Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L23-L101), [apps/web/app/api/analytics/export/route.ts:21-128](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/analytics/export/route.ts#L21-L128), [apps/web/app/api/links/export/route.ts:48-60](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/links/export/route.ts#L48-L60)

> [!NOTE]
> Analytics exports (`/api/analytics/export`) bypass the QStash threshold check entirely, utilizing an extended route segment config `maxDuration = 300` to stream zipped multi-endpoint analytics payloads directly within the HTTP connection.
> Sources: [apps/web/app/api/analytics/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/analytics/export/route.ts#L21-L128)

> [!WARNING]
> Background export workers authenticate incoming QStash payloads via `verifyQstashSignature` or `withCron`, rejecting unverified HTTP posts before parsing payload zulu schemas or querying user records.
> Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L25-L30), [apps/web/app/(ee)/api/cron/export/customers/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app/(ee)/api/cron/export/customers/route.ts#L18-L22)

## Synchronous Workspace CSV Endpoints

### Synchronous Workspace CSV Endpoints

### Overview

Workspace-authenticated synchronous CSV endpoints handle direct file downloads for links, commissions, payouts, customers, audit logs, and program applications. Each route validates workspace context and permissions using `withWorkspace`, parses query parameters with dedicated Zod schemas, checks volume limits, and either returns a direct CSV response (`200 OK`) or offloads execution to QStash background cron jobs (`202 Accepted`) when dataset sizes exceed defined thresholds.

Sources: [apps/web/app/(ee)/api/commissions/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/commissions/export/route.ts#L15-L63), [apps/web/app/(ee)/api/payouts/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/payouts/export/route.ts#L14-L59), [apps/web/app/api/links/export/route.ts:22-96](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/links/export/route.ts#L22-L96), [apps/web/app/(ee)/api/customers/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app/(ee)/api/customers/export/route.ts#L16-L72), [apps/web/app/(ee)/api/audit-logs/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/audit-logs/export/route.ts#L16-L60), [apps/web/app/(ee)/api/program-applications/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/program-applications/export/route.ts#L24-L93)

### Endpoint Reference and Constraints

The synchronous export routes enforce strict access controls, plan restrictions, and volume limits before querying or formatting records.

| Route Path | Method | Max Synchronous Limit | Plan / Permission Requirements | QStash Offload URL |
| :--- | :--- | :--- | :--- | :--- |
| `/api/links/export` | `GET` | 1,000 links | `links.read` permission | `/api/cron/export/links` |
| `/api/commissions/export` | `GET` | 1,000 commissions | Default program ID | `/api/cron/export/commissions` |
| `/api/payouts/export` | `GET` | 1,000 payouts | Default program ID | `/api/cron/export/payouts` |
| `/api/customers/export` | `GET` | 1,000 customers | `business`, `advanced`, `enterprise` | `/api/cron/export/customers` |
| `/api/audit-logs/export` | `POST` | None (All queried) | `enterprise`, roles `owner`/`member` | None (Direct only) |
| `/api/program-applications/export` | `GET` | None (All queried) | `business`, `advanced`, `enterprise` | None (Direct only) |

Sources: [apps/web/app/(ee)/api/commissions/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/commissions/export/route.ts#L12-L41), [apps/web/app/(ee)/api/payouts/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/payouts/export/route.ts#L11-L39), [apps/web/app/api/links/export/route.ts:19-60](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/links/export/route.ts#L19-L60), [apps/web/app/(ee)/api/customers/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app/(ee)/api/customers/export/route.ts#L13-L49), [apps/web/app/(ee)/api/audit-logs/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/audit-logs/export/route.ts#L16-L60), [apps/web/app/(ee)/api/program-applications/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/program-applications/export/route.ts#L24-L93)

### Call-Chain Execution Walkthrough

For workspace link exports, the synchronous request execution follows a precise validation and query sequence:

`withWorkspace()` -> `throwIfClicksUsageExceeded(workspace)` -> `linksExportQuerySchema.parse(searchParams)` -> `validateLinksQueryFilters()` -> `getStartEndDates()` -> `getLinksCount()` -> Threshold check (`linksCount > 1000`? If yes: `qstash.publishJSON()` -> `202 Accepted`) -> `getLinksForWorkspace()` -> `formatLinksForExport()` -> `convertToCSV()` -> `new Response(csvData)` with headers.

Sources: [apps/web/app/api/links/export/route.ts:22-92](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/links/export/route.ts#L22-L92)

### Design Trade-Offs

| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| Fixed synchronous export threshold (`MAX_*_TO_EXPORT = 1000`) | Protects server memory and request timeout limits by redirecting large reports to background workers. | Datasets slightly above 1,000 records require users to wait for asynchronous email delivery instead of immediate download. |
| Workspace permission wrappers (`withWorkspace`) | Centralizes tenant isolation, session extraction, and permission checks (`links.read`, plan checks). | Couples route handlers tightly to authentication middleware wrappers. |
| Dynamic schema column ordering (e.g. Program Applications) | Automatically aligns exported CSV headers with defined UI column configuration and sort maps. | Adds CPU overhead to map, sort, and parse record keys on every export request. |

Sources: [apps/web/app/(ee)/api/commissions/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/commissions/export/route.ts#L12-L41), [apps/web/app/api/links/export/route.ts:22-96](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/links/export/route.ts#L22-L96), [apps/web/app/(ee)/api/program-applications/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/program-applications/export/route.ts#L58-L81)

> [!WARNING]
> Link exports automatically tighten query parameters for large workspaces: when workspace total links exceed `SORTABLE_LINKS_LIMIT`, `sortBy` forces to `"createdAt"`, and when total links exceed `MEGA_WORKSPACE_LINKS_LIMIT`, `searchMode` forces to `"exact"`.
> Sources: [apps/web/app/api/links/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/links/export/route.ts#L68-L73)

> [!NOTE]
> Audit log exports (`/api/audit-logs/export`) use an HTTP `POST` method requiring a JSON body with `start` and `end` date strings, explicitly validating plan capabilities via `getPlanCapabilities(workspace.plan)` before querying records.
> Sources: [apps/web/app/(ee)/api/audit-logs/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/audit-logs/export/route.ts#L16-L36)

## Asynchronous Cron and Worker Pipelines

### Overview

Large-scale background data exports are driven by asynchronous QStash cron worker endpoints located under `apps/web/app/(ee)/api/cron/export/`. These workers process massive data volumes—such as commissions, payouts, links, customers, and partners—by fetching records in memory-safe asynchronous batches, converting them to CSV format, storing them as downloadable artifacts, and notifying users via email upon completion.

Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L22-L105), [apps/web/app/(ee)/api/cron/export/payouts/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/payouts/route.ts#L21-L111), [apps/web/app/(ee)/api/cron/export/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/links/route.ts#L27-L147), [apps/web/app/(ee)/app/(ee)/api/cron/export/customers/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/route.ts#L18-L98), [apps/web/app/(ee)/api/cron/export/partners/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/partners/route.ts#L22-L105)

### QStash Verification and Request Pipeline

The asynchronous worker endpoints utilize two distinct middleware mechanisms for authentication and payload verification: manual signature validation via `verifyQstashSignature` or wrapper-based execution via `withCron`. 

For manual signature verification routes (commissions, links, and partners), the incoming request text is read and verified against QStash headers before JSON payload parsing and Zod schema validation. Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L25-L34), [apps/web/app/(ee)/api/cron/export/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/links/route.ts#L30-L39), [apps/web/app/(ee)/api/cron/export/partners/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/partners/route.ts#L25-L34)

Routes configured with `withCron` (payouts and customers) handle signature verification and request lifecycle wrapping implicitly. Sources: [apps/web/app/(ee)/api/cron/export/payouts/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/payouts/route.ts#L21-L22), [apps/web/app/(ee)/app/(ee)/api/cron/export/customers/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/route.ts#L18-L19)

### Batch Processing and Execution Flow

To prevent memory exhaustion during large-scale data retrieval, workers iterate over asynchronous generators that fetch records in controlled batches. 

| Export Endpoint | Batch Fetcher Function | Formatting Function | Hard Record Limit |
| :--- | :--- | :--- | :--- |
| `/api/cron/export/commissions` | `fetchCommissionsBatch()` | `formatCommissionsForExport()` | None (Full query) |
| `/api/cron/export/payouts` | `fetchPayoutsBatch()` | `formatPayoutsForExport()` | `100_000` |
| `/api/cron/export/links` | `fetchLinksBatch()` | `formatLinksForExport()` | None (Full query) |
| `/api/cron/export/customers` | `fetchCustomersBatch()` | `formatCustomersForExport()` | `100_000` |
| `/api/cron/export/partners` | `fetchPartnersBatch()` | `formatPartnersForExport()` | None (Full query) |

Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L75-L79), [apps/web/app/(ee)/api/cron/export/payouts/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/payouts/route.ts#L17-L80), [apps/web/app/(ee)/api/cron/export/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/links/route.ts#L119-L121), [apps/web/app/(ee)/app/(ee)/api/cron/export/customers/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/route.ts#L14-L67), [apps/web/app/(ee)/api/cron/export/partners/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/partners/route.ts#L71-L79)

> [!NOTE]
> Payouts and customers worker pipelines enforce an explicit upper export bound (`MAX_PAYOUTS_EXPORT_LIMIT` and `MAX_CUSTOMERS_EXPORT_LIMIT` set to `100,000` rows), breaking the batch consumption loop once the accumulator reaches capacity.
> Sources: [apps/web/app/(ee)/api/cron/export/payouts/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/payouts/route.ts#L17-L77), [apps/web/app/(ee)/app/(ee)/api/cron/export/customers/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/route.ts#L14-L64)

> [!WARNING]
> Link exports automatically adjust `searchMode` during filter construction when `workspace.totalLinks` exceeds `MEGA_WORKSPACE_LINKS_LIMIT`, forcing exact searches instead of fuzzy matching for performance stability.
> Sources: [apps/web/app/(ee)/api/cron/export/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/links/route.ts#L109-L111)

## ZIP Archive Packaging for Analytics

### Overview

Analytics exports bundle multi-endpoint analytics datasets into a downloadable ZIP archive. The pipeline queries multiple analytics groupings concurrently or sequentially, transforms response rows into CSV format, packages them via `jszip`, and returns a buffered node stream with `application/zip` headers. The primary route handler executes at `/api/analytics/export`, backed by workspace authentication, rate limiting policies (`RATELIMIT_POLICIES.analyticsExport`), and plan capability validations. A partner-profile counterpart located at `/api/partner-profile/programs/[programId]/analytics/export` applies partner-specific enrollment checks, rate limiting (`RATELIMIT_POLICIES.partnerAnalyticsExport`), and row formatters.

Sources: [apps/web/app/api/analytics/export/route.ts:18-132](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/analytics/export/route.ts#L18-L132), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/analytics/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/analytics/export/route.ts#L26-L136), [apps/web/lib/analytics/export-analytics-to-zip.ts:52-93](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/analytics/export-analytics-to-zip.ts#L52-L93)

### Multi-Endpoint Aggregation and Call-Chain Execution

The export workflow orchestrates parameter normalization, analytics retrieval across grouped endpoints, row formatting, and ZIP file generation. 

The execution walkthrough follows this exact call chain:
1. `GET` route handler receives request parameters, enforces rate limits, checks click usage, parses queries with `parseAnalyticsQuery` or `partnerProfileAnalyticsQuerySchema`, and verifies workspace or program folders.
2. `exportAnalyticsToZip()` initializes an instance of `JSZip` and calls `getAnalyticsExportEndpoints()` to determine which analytics dimensions to query.
3. Iterating over each resulting endpoint, `getAnalytics()` fetches data with query filters, composite event configurations, and custom date range overrides.
4. If rows are returned, `formatRows()` transforms them if a formatter is provided (such as `formatProgramAnalyticsForExport` or `formatPartnerAnalyticsForExport`).
5. `convertToCSV()` transforms the record array into a CSV string.
6. `zip.file(`${endpoint}.csv`, ...)` appends the CSV to the archive, and `zip.generateAsync({ type: "nodebuffer" })` resolves the final buffer returned to the HTTP response.

Sources: [apps/web/app/api/analytics/export/route.ts:33-121](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/analytics/export/route.ts#L33-L121), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/analytics/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/analytics/export/route.ts#L61-L128), [apps/web/lib/analytics/export-analytics-to-zip.ts:52-93](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/analytics/export-analytics-to-zip.ts#L52-L93)

> [!NOTE]
> The `maxDuration` exported constant on both API route files is explicitly configured to `300` seconds to accommodate large multi-endpoint query aggregation jobs.
> Sources: [apps/web/app/api/analytics/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/api/analytics/export/route.ts#L21), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/analytics/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/analytics/export/route.ts#L23)

### Endpoint Configuration and Exclusions

The packaging logic filters default analytics endpoints by omitting specified exclusions or skipping single-link top link breakdowns. Partner profile endpoints exclude broader partner metadata dimensions.

| Constant Name | Value / Members | Purpose |
| :--- | :--- | :--- |
| `DEFAULT_SKIP_ENDPOINTS` | `["count"]` | Excludes the basic count endpoint from standard analytics ZIP archives. |
| `PARTNER_PROFILE_SKIP_ENDPOINTS` | `["count", "top_partners", "top_groups", "top_partner_tags", "top_folders", "top_link_tags"]` | Excludes non-applicable dimensions when exporting partner profile analytics. |

Sources: [apps/web/lib/analytics/export-analytics-to-zip.ts:10-19](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/analytics/export-analytics-to-zip.ts#L10-L19)

### Client-Side Trigger and Blob Handling

The client interacts with the export endpoints via `AnalyticsExportButton`, which invokes the dynamic route based on whether a partner page context is active, triggers a toast promise, handles blob response creation, and programmatically initiates file downloads. Sources: [apps/web/ui/analytics/analytics-export-button.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/analytics/analytics-export-button.tsx#L7-L76)

## Click and Event Log Exports

### Overview

Click and event log exports handle filtering, pagination, and column projection across Tinybird event logs and raw click streams. The system supports both synchronous responses for smaller datasets and asynchronous background tasks via QStash when event counts exceed designated thresholds.

Sources: [apps/web/app/(ee)/api/cron/export/events/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/partner/route.ts#L38-L187), [apps/web/app/(ee)/api/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/events/export/route.ts#L36-L168), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/events/export/route.ts#L35-L203), [apps/web/app/(ee)/api/cron/export/events/workspace/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/workspace/route.ts#L30-L91)

### Threshold Control and Pipeline Execution

The event export route begins by validating plan requirements and querying analytics counts. If the matched events exceed `MAX_EVENTS_TO_EXPORT`, the job dispatches a background task to QStash and immediately returns an HTTP 202 response. Otherwise, it retrieves the event stream synchronously, maps requested columns using accessors, and outputs a downloadable CSV.

```mermaid
graph TD
    A[Client Request] --> B{Count > MAX_EVENTS_TO_EXPORT?}
    B -->|Yes| C[Publish QStash Background Job]
    C --> D[Return HTTP 202 Accepted]
    B -->|No| E[Fetch Events via getEvents]
    E --> F[Map Columns & Format CSV]
    F --> G[Return CSV Response]
```

Sources: [apps/web/app/(ee)/api/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/events/export/route.ts#L36-L175), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/events/export/route.ts#L35-L203)

### Configuration Constants and Default Columns

Event export behavior is regulated by strict limits and preset column mappings for different event types.

| Constant Name | Value / Type | Purpose |
| :--- | :--- | :--- |
| `MAX_EVENTS_TO_EXPORT` | `1000` | Threshold that determines whether to process an event export synchronously or offload to background QStash workers. |
| `MAX_PARTNER_LINKS_FOR_LOCAL_FILTERING` | Constant from `@dub/constants` | Maximum number of partner links allowed before switching from explicit local link evaluation to partner-level query filters. |
| `defaultColumns["clicks"]` | `["timestamp", "link", "referer", "country", "device"]` | Default projected columns when exporting click events without an explicit column parameter. |
| `defaultColumns["leads"]` | `["timestamp", "event", "link", "customer", "referer"]` | Default projected columns when exporting lead events. |
| `defaultColumns["sales"]` | `["timestamp", "saleAmount", "event", "customer", "referer", "link"]` | Default projected columns when exporting sale events. |

Sources: [apps/web/app/(ee)/api/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/events/export/route.ts#L25-L34), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/events/export/route.ts#L14-L17), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/events/export/route.ts#L80-L91)

> [!WARNING]
> Requests exceeding `1000` events bypass synchronous payload rendering entirely. They publish JSON payloads to QStash endpoints (`/api/cron/export/events/workspace` or `/api/cron/export/events/partner`) and return HTTP 202 status codes, requiring clients to poll or retrieve results via generated email notifications.
> Sources: [apps/web/app/(ee)/api/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/events/export/route.ts#L136-L149), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/events/export/route.ts#L173-L190)

### Column Projection and Transformation

When formatting event log rows, the system evaluates dynamic column accessors and column names via helper mappings, falling back to direct property access and capitalized keys. Partner-specific export workers enforce strict sanitization rules, dropping IP addresses from event payloads and obfuscating customer email fields unless explicit data sharing consent is active. Sources: [apps/web/app/(ee)/api/cron/export/events/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/partner/route.ts#L176-L183), [apps/web/app/(ee)/api/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/events/export/route.ts#L159-L166), [apps/web/app/(ee)/api/cron/export/events/workspace/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/workspace/route.ts#L82-L89)

## Partner Program Exports and Privacy

### Overview

Partner program data exports provide specialized routes for partners to export events and customer records associated with specific programs. These endpoints enforce strict program enrollment verification, check minimum commission thresholds for large programs, and implement privacy safeguards such as customer email obfuscation and pseudorandom name generation when data sharing is disabled.

Sources: [apps/web/app/(ee)/api/cron/export/events/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/partner/route.ts#L68-L76), [apps/web/app/(ee)/api/cron/export/customers/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/partner/route.ts#L56-L63), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/events/export/route.ts#L38-L57), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/customers/export/route.ts#L26-L44)

### Enrollment Checks and Threshold Rules

Before processing any partner export, the request pipeline validates partner enrollment and enforces tier-based restrictions. Large programs require a minimum total commission amount in cents before export features are enabled.

| Constant Name | Value / Type | Purpose |
| :--- | :--- | :--- |
| `MAX_EVENTS_TO_EXPORT` | `1000` | Maximum event count for synchronous partner event exports before offloading via QStash. |
| `MAX_CUSTOMERS_TO_EXPORT` | `1000` | Maximum customer count for synchronous partner customer exports before background processing. |
| `MAX_CUSTOMERS_EXPORT_LIMIT` | `100_000` | Hard cap on total customer records processed by the background customer export worker. |
| `PAGE_SIZE` | `1000` | Chunk size used when paginating database queries in background customer export loops. |

Sources: [apps/web/app/(ee)/api/cron/export/events/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/partner/route.ts#L38-L49), [apps/web/app/(ee)/api/cron/export/customers/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/partner/route.ts#L16-L17), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/events/export/route.ts#L33-L37), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/customers/export/route.ts#L18-L18)

> [!WARNING]
> If a partner belongs to a program included in `LARGE_PROGRAM_IDS`, the total commissions converted to cents via `toCentsNumber(totalCommissions)` must meet or exceed `LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS`. Otherwise, the API throws a `forbidden` `DubApiError`.
> Sources: [apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/events/export/route.ts#L48-L57), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/customers/export/route.ts#L35-L44)

### Customer Email Obfuscation and Aliasing

Partner event and customer exports protect end-user privacy when customer data sharing is disabled. IP addresses are systematically stripped from both click and event payloads. Customer email addresses are obfuscated using `obfuscateCustomerEmail`, and missing names or obfuscated records fall back to pseudorandomly generated names via `generateRandomName`. Sources: [apps/web/app/(ee)/api/cron/export/events/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/partner/route.ts#L144-L171), [apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/export/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/events/export/route.ts#L205-L225)

> [!TIP]
> When `customerDataSharingEnabledAt` is present and active, raw customer emails and real customer names are preserved in the export. When absent, email addresses are masked and names default to generated pseudorandom identifiers.
> Sources: [apps/web/app/(ee)/api/cron/export/events/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/partner/route.ts#L154-L171), [apps/web/app/(ee)/api/cron/export/customers/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/partner/route.ts#L56-L80)

## Downloadable Artifact Storage and Delivery

### Overview

Once large datasets for commissions, payouts, customers, links, partners, and workspace events are gathered and formatted into CSV strings, the background worker pipeline stores the resulting file and notifies the user via email. This process coordinates signature verification, file key generation, signed storage uploads, and React-based email templates.

Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L83-L101), [apps/web/app/(ee)/api/cron/export/payouts/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/payouts/route.ts#L84-L102), [apps/web/app/(ee)/api/cron/export/customers/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/partner/route.ts#L127-L145), [apps/web/app/(ee)/api/cron/export/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/links/route.ts#L125-L143), [apps/web/app/(ee)/app/(ee)/api/cron/export/customers/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/route.ts#L71-L89), [apps/web/app/(ee)/api/cron/export/partners/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/partners/route.ts#L83-L101), [apps/web/app/(ee)/api/cron/export/events/workspace/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/workspace/route.ts#L95-L113)

### Export Delivery Call Chain and Storage Paths

Background export handlers follow a structured delivery sequence from raw body ingestion to recipient notification. 

`verifyQstashSignature()` -> `prisma.user.findUnique()` -> `createDownloadableExport()` -> `sendEmail()` -> `ExportReady()`

Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L27-L101), [apps/web/app/(ee)/api/cron/export/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/links/route.ts#L32-L143), [apps/web/app/(ee)/api/cron/export/partners/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/partners/route.ts#L27-L101)

Storage keys are generated using randomized suffixes combined with entity-specific subdirectories via `generateRandomString(16)` and `generateExportFilename()`.

| Export Domain | File Key Template | Content Type |
| :--- | :--- | :--- |
| Commissions | `exports/commissions/${generateRandomString(16)}.csv` | `text/csv` |
| Payouts | `exports/payouts/${generateRandomString(16)}.csv` | `text/csv` |
| Partner Customers | `exports/customers/partner/${generateRandomString(16)}.csv` | `text/csv` |
| Links | `exports/links/${generateRandomString(16)}.csv` | `text/csv` |
| Customers | `exports/customers/${generateRandomString(16)}.csv` | `text/csv` |
| Partners | `exports/partners/${generateRandomString(16)}.csv` | `text/csv` |
| Workspace Events | `exports/events/workspace/${generateRandomString(16)}.csv` | `text/csv` |

Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L83-L88), [apps/web/app/(ee)/api/cron/export/payouts/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/payouts/route.ts#L84-L89), [apps/web/app/(ee)/api/cron/export/customers/partner/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/partner/route.ts#L127-L132), [apps/web/app/(ee)/api/cron/export/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/links/route.ts#L125-L130), [apps/web/app/(ee)/app/(ee)/api/cron/export/customers/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/customers/route.ts#L71-L76), [apps/web/app/(ee)/api/cron/export/partners/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/partners/route.ts#L83-L88), [apps/web/app/(ee)/api/cron/export/events/workspace/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/events/workspace/route.ts#L95-L100)

> [!WARNING]
> If the target user record cannot be found or lacks an associated email address during export processing, the execution terminates early and logs a skip message rather than throwing an unhandled exception.
> Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L45-L51), [apps/web/app/(ee)/api/cron/export/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/links/route.ts#L50-L56), [apps/web/app/(ee)/api/cron/export/partners/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/partners/route.ts#L45-L51)

### Email Notification Structure

Once `createDownloadableExport` returns the `downloadUrl`, the worker dispatches a notification using `@dub/email` with the `ExportReady` React template. The payload passes the recipient email, export type identifier, download URL, and contextual workspace or program metadata. Sources: [apps/web/app/(ee)/api/cron/export/commissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/commissions/route.ts#L90-L101), [apps/web/app/(ee)/api/cron/export/links/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/links/route.ts#L132-L143), [apps/web/app/(ee)/api/cron/export/partners/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/export/partners/route.ts#L90-L101)

## Related

- [Analytics Dashboard and Querying](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/analytics-and-tracking/analytics-dashboard-and-querying)
- [Background Jobs and Queues](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/core-architecture/background-jobs-and-queues)


## Sitemap

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