> ## Documentation Index
> Fetch the complete documentation index at: https://docs.topsort.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Asynchronous Exports

> Ask for a file of your advertising results and download it when it is ready.

export const IntegrationsCard1A = () => {
  return <>
      <style>{`
        .integrations-card-1a {
          display: flex;
          flex-direction: column;
          background-color: #f0f2f4;
          border: 1px solid rgba(0, 0, 0, 0.08);
          border-radius: 12px;
          padding: 1.25rem 1.5rem;
          gap: 0.25rem;
          color: #25253b;
          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
          transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
          margin-top: 2rem;
        }

        .integrations-card-1a:hover {
          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
          border-color: rgba(0, 0, 0, 0.12);
        }

        .dark .integrations-card-1a {
          background-color: #25253b;
          color: #f6f8fa;
          border-color: rgba(255, 255, 255, 0.08);
          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
        }

        .dark .integrations-card-1a:hover {
          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
          border-color: rgba(255, 255, 255, 0.12);
        }

        .integrations-card-1a-avatar-row {
          display: flex;
          margin-left: 14px;
          flex-wrap: nowrap;
        }

        .integrations-card-1a-avatar {
          width: 44px;
          height: 44px;
          border-radius: 50%;
          border: 2px solid #f0f2f4;
          margin-left: -14px;
          object-fit: cover;
          box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
          pointer-events: none;
          cursor: default;
        }

        .dark .integrations-card-1a-avatar {
          border-color: #25253b;
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
        }

        .integrations-card-1a h2 {
          margin: 0;
          line-height: 1.25;
          font-size: clamp(1.25rem, calc(0.875rem + 1.25vw), 1.75rem);
          font-weight: 700;
          letter-spacing: -0.025em;
          color: #111827;
        }

        .dark .integrations-card-1a h2 {
          color: #f9fafb;
        }

        .integrations-card-1a-subtitle {
          font-size: 0.9375rem;
          font-weight: 500;
          line-height: 1.5;
          color: #4b5563;
          margin: 0;
        }

        .dark .integrations-card-1a-subtitle {
          color: #d1d5db;
        }

        .integrations-card-1a p {
          margin: 0;
          line-height: 1.5;
          font-size: 0.875rem;
          color: #374151;
        }

        .dark .integrations-card-1a p {
          color: #d1d5db;
        }

        .integrations-card-1a a {
          color: #0500ff;
          text-decoration: none;
          font-weight: 500;
          transition: color 0.15s ease;
        }

        .integrations-card-1a a:hover {
          color: #0400cc;
        }

        .dark .integrations-card-1a a {
          color: #8280ff;
        }

        .dark .integrations-card-1a a:hover {
          color: #a19fff;
        }

        @media (max-width: 640px) {
          .integrations-card-1a-avatar {
            width: 38px;
            height: 38px;
            margin-left: -12px;
          }

          .integrations-card-1a-avatar-row {
            margin-left: 12px;
          }
        }

        @media (min-width: 1280px) {
          .integrations-card-1a p {
            font-size: 0.6875rem;
            line-height: 1.4;
            letter-spacing: -0.01em;
          }

          .integrations-card-1a h2 {
            font-size: clamp(1rem, calc(0.75rem + 1vw), 1.25rem);
          }

          .integrations-card-1a-subtitle {
            font-size: 0.75rem;
          }
        }
      `}</style>
      <div className="integrations-card-1a">
        <div className="integrations-card-1a-avatar-row">
          <img src="/images/integration-team/agustin.webp" alt="Agustin - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/amelia.webp" alt="Amelia - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/anais.webp" alt="Anais - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/bernardita.webp" alt="Bernardita - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/felipe.webp" alt="Felipe - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/gui.webp" alt="Gui - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/javiera.webp" alt="Javiera - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/linn.webp" alt="Linn - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/marcio.webp" alt="Marcio - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/max.webp" alt="Max - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/natalie.webp" alt="Natalie - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/rodrigo.webp" alt="Rodrigo - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
          <img src="/images/integration-team/tomas.webp" alt="Tomás - Integrations Team Member" loading="lazy" className="integrations-card-1a-avatar" />
        </div>

        <h2>Need help with your Topsort integration?</h2>

        <p className="integrations-card-1a-subtitle">
          Talk to us, the integrations engineering team.
        </p>

        <p>
          Reach us via <strong>your dedicated messaging channel</strong> or at{" "}
          <a href="mailto:support@topsort.com">support@topsort.com</a>.
        </p>

        <p>
          If you're not a customer yet,{" "}
          <a href="https://www.topsort.com/book-a-demo">book a demo</a> with our
          sales team instead.
        </p>
      </div>
    </>;
};

<Note>
  Asynchronous Exports is available on Topsort's [public API](/en/api-reference/export-api).
  Use an [Advanced API key](/en/api-reference/authentication) to call it.
</Note>

An export is a file with the advertising numbers you choose. For example: impressions, clicks, and spend for each vendor and campaign, one row per day, for the month of September.

You do not wait while Topsort builds the file. You ask for it, check back until it is ready, and then download it. The file is Parquet. Programs such as Python can open it as a table.

For the list of dimensions, metrics, limits, and common questions, see [Asynchronous Exports](/en/knowledge-base/ad-platform/reporting-and-analytics/custom-data-exports) in the Knowledge Base.

## Before you start

You need an Advanced API key. The API address is `https://api.topsort.com`.

The examples use `curl`. Save these once, so you can reuse them:

```bash theme={null}
export TOPSORT_API_URL="https://api.topsort.com"
export TOPSORT_TOKEN="<your-advanced-api-key>"
```

Every request sends the key in this header:

```text theme={null}
Authorization: Bearer <your-advanced-api-key>
```

If the header is missing, or the token is not valid, any endpoint answers `401 Unauthorized`:

```json theme={null}
{ "detail": "Not authenticated" }
```

## 1. See what you can export

Ask which datasets exist and which columns each one allows. This call takes no parameters.

A dataset is a kind of report. A dimension is how each row is split, such as the day or the vendor. A metric is a number in that row, such as clicks or spend.

```bash theme={null}
curl -s "$TOPSORT_API_URL/public/v1/export-service/datasets" \
  -H "Authorization: Bearer $TOPSORT_TOKEN"
```

Response `200 OK`, shortened:

```json theme={null}
{
  "results": [
    {
      "dataset": "monitoring_metrics",
      "description": "Advertising performance by campaign, placement, and device, bucketed into local days.",
      "dimensions": ["vendor_id", "campaign_id", "day_local", "device_type", "..."],
      "measures": ["impressions", "clicks", "ad_spend", "roas", "..."],
      "filters": ["day_local"],
      "granularities": ["day_local"]
    }
  ]
}
```

| Field | Use it for |
| - | - |
| `dataset` | The value you send as `dataset` in step 2 |
| `dimensions` | The values you can send in `dimensions` |
| `measures` | The values you can send in `metrics` |
| `filters` | The dimension that `startDate` and `endDate` filter on |
| `granularities` | The values you can send in `granularity` |

If something fails on our side, the call returns `500` with `internal_error`. Contact our customer support team.

## 2. Ask for the file

Send the dataset, the columns you want, and the days you want.

```bash theme={null}
curl -s -X POST "$TOPSORT_API_URL/public/v1/export-service/exports" \
  -H "Authorization: Bearer $TOPSORT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset": "monitoring_metrics",
    "dimensions": ["day_local", "vendor_id", "campaign_id"],
    "metrics": ["impressions", "clicks", "ad_spend", "roas"],
    "granularity": "day_local",
    "startDate": "2026-09-01",
    "endDate": "2026-09-30"
  }'
```

| Field | Required | Rules |
| - | - | - |
| `dataset` | Yes | One of the datasets from step 1 |
| `dimensions` | Yes | 1 to 27 values from that dataset's `dimensions`. Send each value once. |
| `metrics` | Yes | 1 to 19 values from that dataset's `measures`. Send each value once. |
| `granularity` | Yes | How time is grouped. Today only `day_local`: one row per local day. |
| `startDate` | Yes | `YYYY-MM-DD`, inclusive. At most 120 days before today (UTC). |
| `endDate` | Yes | `YYYY-MM-DD`, inclusive, on or after `startDate`. The window can cover up to 31 days, counting both dates. |

Dates are local days of your marketplace. To export one day, send the same date as both `startDate` and `endDate`. Columns in the file follow the order you send: dimensions first, then metrics.

<Warning>
  If the window includes today, today's numbers can still change later.
</Warning>

Response `201 Created`:

```json theme={null}
{
  "exportId": "218ad339-00cd-4e11-aeaa-8d96694908a0",
  "status": "pending",
  "dataset": "monitoring_metrics",
  "createdAt": "2026-09-08T14:03:12Z"
}
```

Save `exportId`. You need it for the next step. Each accepted export counts toward your daily quota of 20, which resets at midnight UTC.

**`400 Bad Request`** means the request breaks an export rule:

| `code` | Cause | What to do |
| - | - | - |
| `duplicate_dimension` / `duplicate_measure` | A value was sent twice | Send each value once |
| `invalid_date_range` | `endDate` is before `startDate` | Fix the window |
| `date_range_too_large` | The window is longer than 31 days | Split it into several exports |
| `date_range_too_old` | `startDate` is more than 120 days ago | Start on or after `earliestDate` in `details` |

```json theme={null}
{
  "error": {
    "code": "date_range_too_large",
    "message": "The day_local window exceeds the 31 day limit",
    "details": [{ "days": 40, "maxDays": 31 }]
  }
}
```

**`429 Too Many Requests`** (`daily_request_limit_exceeded`) means you used today's quota. Wait until `resetsAt` in `details`. The `Retry-After` header says how many seconds are left.

**`422 Unprocessable Entity`** (`validation_error`) means a field is missing, a list is empty, a dataset, dimension, metric, or granularity does not exist, or a date is not `YYYY-MM-DD`. `details` points to the field to fix.

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": [
      {
        "type": "enum",
        "loc": ["body", "dimensions", 0],
        "msg": "Input should be 'vendor_id', 'campaign_id', ...",
        "input": "vendor"
      }
    ]
  }
}
```

## 3. Check until the file is ready

Ask for the export with the `exportId` from step 2.

```bash theme={null}
export EXPORT_ID="218ad339-00cd-4e11-aeaa-8d96694908a0"

curl -s "$TOPSORT_API_URL/public/v1/export-service/exports/$EXPORT_ID" \
  -H "Authorization: Bearer $TOPSORT_TOKEN"
```

While it is `pending` or `running`, the response has no download link yet:

```json theme={null}
{
  "exportId": "218ad339-00cd-4e11-aeaa-8d96694908a0",
  "status": "running",
  "dataset": "monitoring_metrics",
  "createdAt": "2026-09-08T14:03:12Z"
}
```

Check again at most once a minute. Wait longer between checks for exports that take a while. Most exports finish in less than an hour.

When it is `completed` and has rows, the response includes `downloadUrl`:

```json theme={null}
{
  "exportId": "218ad339-00cd-4e11-aeaa-8d96694908a0",
  "status": "completed",
  "dataset": "monitoring_metrics",
  "createdAt": "2026-09-08T14:03:12Z",
  "downloadUrl": "https://downloads.example.com/exports/218ad339-00cd-4e11-aeaa-8d96694908a0.parquet?expires=3600&signature=...",
  "downloadExpiresAt": "2026-09-08T15:21:40Z"
}
```

When it is `completed` and no rows matched, there is no file and no `downloadUrl`. The export still finishes as `completed`. `message` explains why.

```json theme={null}
{
  "exportId": "218ad339-00cd-4e11-aeaa-8d96694908a0",
  "status": "completed",
  "dataset": "monitoring_metrics",
  "createdAt": "2026-09-08T14:03:12Z",
  "message": "No data was found for the requested date range."
}
```

When it is `failed`, `error` says why. Failed exports are not retried. Request a new one. Use a shorter window or fewer dimensions if it failed because it took too long or the file was too large.

```json theme={null}
{
  "exportId": "218ad339-00cd-4e11-aeaa-8d96694908a0",
  "status": "failed",
  "dataset": "monitoring_metrics",
  "createdAt": "2026-09-08T14:03:12Z",
  "error": "Export exceeded the processing time limit"
}
```

| Status | `code` | Cause |
| - | - | - |
| `404` | `export_not_found` | No export with that ID exists |
| `422` | `validation_error` | `export_id` is missing or is not a valid ID |
| `500` | `internal_error` | Something went wrong on our side. Contact our customer support team |

## 4. Download the file

Download the Parquet file from `downloadUrl`. The link already carries its own signature, so do not send the `Authorization` header.

```bash theme={null}
curl -s -o export.parquet "<downloadUrl>"
```

The link works for 1 hour from the moment you receive it, until `downloadExpiresAt`. If it expires, repeat step 3 to get a new link. You do not need to create a new export.

To take a quick look at the file with Python:

```python theme={null}
import pandas as pd

df = pd.read_parquet("export.parquet")
print(df.head())
```

`head()` prints the first rows, so you can confirm the columns and the dates before you load the file anywhere else.

## Endpoints

| Step | Method and path | Sends | Returns |
| - | - | - | - |
| 1 | `GET /public/v1/export-service/datasets` | Nothing | The datasets and what each one accepts |
| 2 | `POST /public/v1/export-service/exports` | JSON body with the export | `exportId` and `pending` status |
| 3 | `GET /public/v1/export-service/exports/{export_id}` | Export ID | Status, `downloadUrl` when it completed with rows, or `message` when it completed with none |
| 4 | `GET <downloadUrl>` | Nothing | The Parquet file |

<IntegrationsCard1A />


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.