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

# Export API: Bulk Catalog Ingestion

> Export the Snappy catalog in bulk via asynchronous NDJSON jobs. Filter by product or scope to a collection, then poll for the signed download URL.

The **Export API** powers bulk catalog ingestion. Instead of paging through `/v3/products` or `/v3/collections/{collectionId}/products` to mirror the Snappy catalog into your own platform, you queue an asynchronous export job and pull a single NDJSON file containing the full result set.

Export is the recommended way to keep a local product mirror up to date - pair a nightly bulk export with [Webhooks](/pages/webhook-event-types) for incremental product changes between exports.

<Tip>
  Want to understand how Export fits into the bigger picture? See [Snappy Core Concepts & Data Models](/pages/snappy-core-concepts-and-data-models).
</Tip>

<Note>
  Exports are **NDJSON-only** for now. Each line in the file is a single JSON object representing one product, with the same shape as `GET /v3/products/{productId}` (plus any `include` / `fields` expansions).
</Note>

***

## When to use Export

Use the Export API when:

* You need the **entire catalog** (or a large filtered subset) in a single payload - typically for nightly ingestion into a partner system, search index, or data warehouse.
* The synchronous list endpoints would require dozens or hundreds of paginated requests.
* You'd rather fire-and-forget the job and pick up the result later than hold an HTTP connection open.

Use the synchronous list endpoints (`GET /v3/products`, `GET /v3/collections/{collectionId}/products`) when:

* You need **real-time** browse / search inside your UI.
* The result set is small enough to fit in one or two pages.

***

## How export jobs work

Export is a standard async job pattern:

<Steps>
  <Step title="Create the job">
    Call `POST /v3/products/exports` (filtered export) or `POST /v3/collections/exports` (collection-scoped export). Both return **200 OK** with an `exportId` immediately.
  </Step>

  <Step title="Poll for status">
    Call `GET /v3/products/exports/{exportId}` until the job reaches a terminal state.
  </Step>

  <Step title="Download the file">
    When `status` is `completed`, the response includes a `downloadUrls` map of signed URLs. Download the file(s) before the export record expires.
  </Step>
</Steps>

### Status lifecycle

| Status       | Description                                                                               |
| :----------- | :---------------------------------------------------------------------------------------- |
| `pending`    | Job has been queued; processing has not started.                                          |
| `processing` | Job is actively running.                                                                  |
| `completed`  | Job finished successfully. `downloadUrls` is populated with signed S3 URLs.               |
| `failed`     | Job terminated without producing a file. `errorMessage` contains a human-readable reason. |

***

## Key Concepts & Business Rules

#### Download URLs are signed and short-lived

When a job completes, the poll response returns a `downloadUrls` map (keyed by file identifier or location code). These are signed S3 URLs that **expire 48 hours** after the export record is created. After expiry, you'll need to re-issue a new export job.

#### NDJSON, not JSON

Exports produce newline-delimited JSON - one product object per line. This makes it safe to stream-parse multi-gigabyte exports without holding the whole payload in memory.

#### Filtering uses flat body fields (not the JSON:API `filter[...]` query convention)

V3 list endpoints accept filters as bracketed query parameters (`filter[price][gte]`, `filter[brandId]`). Export jobs accept the same filters as **flat JSON body fields** (`price.gte`, `brandId`) - this is intentional for JSON ergonomics. Same semantics, different surface.

#### Idempotency is not currently supported

Repeated calls to `POST /v3/products/exports` with identical bodies create separate export jobs. If you need idempotency for nightly runs, persist the `exportId` of the latest in-flight job on your side and skip creating a new one when one is already pending.

#### Locations drive price localisation

The `locations` array (ISO 3166-1 alpha-2 country codes) is used to localise prices and availability in the exported file. Defaults to `["US"]` on the products export endpoint; required on the collections export endpoint.

#### Permissions

All Export endpoints require:

Requires: `products:read`

***

## How to Work with Export

**Create a filtered product export job**

```text theme={null}
POST /v3/products/exports
```

Queues an async export of products matching the supplied filters. Required: `catalog`, `format`. Optional filters: `search`, `brandName`, `brandId`, `tagId`, `productIds`, `price.gte`/`price.lte`, `locations`, `include`, `fields`. Returns **200 OK** with an `exportId` to poll.

**Create a collection export job**

```text theme={null}
POST /v3/collections/exports
```

Queues an async export of every product in a single collection. Required: `collectionId`, `catalog`, `locations`. Returns **200 OK** with an `exportId` to poll.

**Poll export job status**

```text theme={null}
GET /v3/products/exports/{exportId}
```

Returns the current status of an export job. When `status` is `completed`, includes a `downloadUrls` map with signed URLs. When `status` is `failed`, includes an `errorMessage`. Polls both products and collections export jobs.

***
