# Export all variant products to CSV

```http
POST https://secure.yoprint.com/v1/api/store/{team_slug}/data_export/export_variant_product_list
```

- API version: v1
- Operation ID: `v1_public_data_export_export_variant_product_list`
- Section: Imports and exports › [Data exports](https://developer.yoprint.com/reference/data-exports.md)
- Web page: https://developer.yoprint.com/reference/data-exports/v1-data-export-export-variant-product-list/
- Authentication: `Authorization: Bearer YOUR_API_KEY`

Starts a CSV export of your team's custom variant products, one row per variant (size and color). Deleted products and variants are left out. It runs in the background: the response is the new export with `status` `pending`.

The columns match the variant product CSV template, plus `status`, the variant's sales status: `On sale` or `Phased out`. `price` is a decimal string with at least 2 decimal places (`5.20`); parse it as a decimal. A zero price is left empty. Send no body for every column, or `columns` to choose them.

## Permission

Any member of the team. Exports do not check the role: every member can export every type, including sales orders, invoices, payments and tax, even a member whose role can't see them in the web app.

## Business rules

- One export of each type at a time for the whole team: while one is `pending` or `generating`, creating another of the same type is a 422 (`duplicate_report`).
- At most 3 exports pending or generating per team, of any type. A fourth is a 429 (`export_capacity`).
- `columns`, when sent, must be a non-empty list of column ids for this type. `GET /v1/api/store/{team_slug}/data_export/definitions` lists them with their labels.
- `idempotency_key`, when sent, must be a string of at most 100 characters. Reusing a key with a different type, different settings or another person is a 422.

## Side effects

- Queues the export and returns it at once with `status` `pending`. The file is built in the background.
- People with the Download Center open see it appear and update live. No email is sent.
- The file is kept for 3 days after it is ready, then deleted. The export stays in the list as history.

## Path parameters

- `team_slug` (string, required): Your team slug, shown as Team ID next to your API key in Profile. Example `your-team`.

## Request body

Content type `application/json`, optional.

Fields:

- `columns` (array of strings, optional): The columns to include, in the order you want them in the file. Omit it for every column in the default order. Repeats are dropped. One of: `"supplier_style_code"`, `"title"`, `"brand"`, `"description"`, `"supplier_code"`, `"price"`, `"weight"`, `"size_id"`, `"size_label"`, `"color_id"`, `"color_label"`, `"color_hex"`, `"status"`. At least 1 item. Items must be unique.
- `idempotency_key` (string, optional): Optional, a string of up to 100 characters. Send the same key to retry the same request safely: you get the export it created, and nothing new starts while that export is `pending`, `generating`, `ready`, or failed with `export_generation_failed`. If it failed with `export_queue_failed` or `export_attempt_expired`, the same key queues it again. Keys are shared by the whole team. At most 100 characters.

### Example request

```bash
curl -X POST 'https://secure.yoprint.com/v1/api/store/your-team/data_export/export_variant_product_list' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Accept: application/json' \
  -H 'X-Yoprint-Decimal-Format: exact-v1' \
  -H 'Content-Type: application/json' \
  -d '{
    "columns": [
      "supplier_style_code",
      "title",
      "size_label",
      "color_label",
      "price"
    ],
    "idempotency_key": "export-variant-product-list-1"
  }'
```

## Response

`200` Successful response.

Fields:

- `data` (object, required)
  - `config` (object | null, required): What you asked for: the dates (and `date_field`) as you sent them, and the `columns` you chose, without repeats, with `schema_version`. Null for customer and product exports sent without `columns`.
    - `columns` (array of strings, optional): Only when you chose columns.
    - `date_field` (string, optional): Sales order exports only.
    - `end_date` (string, optional)
    - `schema_version` (integer, optional): Only when you chose columns.
    - `start_date` (string, optional)
  - `created_at` (string (date-time), required)
  - `created_by` (integer | null, required): A plain number for the person who created it. It is not a hash id and no operation takes it; show `creator.name` instead.
  - `creator` (object | null, required): The person who created it; null if their account was deleted. More of their account fields may appear; rely only on these.
    - `email` (string, optional)
    - `id` (integer, optional): The same plain number as `created_by`, not a hash id.
    - `name` (string, optional)
  - `error_code` (string | null, required): Why it failed. `export_generation_failed`: the file could not be built; create a new export. `export_attempt_expired`: it did not finish in time. `export_queue_failed`: it could not be queued. Null unless `status` is `failed`. One of: `"export_generation_failed"`, `"export_attempt_expired"`, `"export_queue_failed"`.
  - `expires_at` (string (date-time) | null, required): When the download stops working: 3 days after the export became ready. The file is then deleted; the export stays in the list but can't be downloaded.
  - `file_size` (integer | null, required): File size in bytes, once ready.
  - `id` (string, required): The export id. Use it to poll and to download.
  - `progress` (object, required)
    - `completed` (integer, required): Rows written so far, not counting the header row. Updated every 500 rows; product exports update only at the end.
    - `total` (integer | null, required): Rows in the file, not counting the header row. Null until the export is ready: there is no total to show a percentage against while it runs.
  - `status` (string, required): `pending`: queued. `generating`: running. `ready`: the file can be downloaded. `failed`: see `error_code`. One of: `"pending"`, `"generating"`, `"ready"`, `"failed"`.
  - `type` (string, required): One of: `"customer_list"`, `"sales_order_list"`, `"invoice_list"`, `"invoice_item_list"`, `"payment_list"`, `"purchase_order_list"`, `"tax_list"`, `"simple_product_list"`, `"variant_product_list"`.
  - `updated_at` (string (date-time), required)
- `status` (integer, required)
- `success` (boolean, required)

Example response:

```json
{
  "data": {
    "config": {
      "columns": [
        "supplier_style_code",
        "title",
        "size_label",
        "color_label",
        "price"
      ],
      "schema_version": 1
    },
    "created_at": "2026-11-03T09:30:00+00:00",
    "created_by": 1001,
    "creator": {
      "email": "alex@example.com",
      "id": 1001,
      "name": "Alex Rivera"
    },
    "error_code": null,
    "expires_at": null,
    "file_size": null,
    "id": "G59hl28azOL22k",
    "progress": {
      "completed": 0,
      "total": null
    },
    "status": "pending",
    "type": "variant_product_list",
    "updated_at": "2026-11-03T09:30:00+00:00"
  },
  "status": 200,
  "success": true
}
```

## Errors

- `401` The API key is missing or wrong (`unauthenticated`).
- `403` You are not allowed to do this. `api_access_not_in_plan`: your team's plan doesn't include API access (Pro and above, with an active subscription or trial), so every request answers this until the team upgrades. When your role lacks the permission the operation needs, the body is only `{"message": "Insufficient Permission"}`. Other refusals use the error envelope, with `unauthorized` or `user_is_not_authorized`.
- `404` Not found. `page_not_found`: the team slug is unknown, you are not a member of the team, or an id in the path is not one of this team's. `code: null`, with a message that starts "No query results for": there is no record with that id.
- `422` The request was refused and changed nothing. `validation_failed` lists what to fix in `error.fields`; other codes name a business rule, explained in `error.message`. A few checks answer with only `{"message": "..."}`. An operation that calls another service (a payment provider, a carrier, an accounting app) can answer `connect_timeout` ("Please retry again") when that service does not answer in time: the change may or may not have happened there, so read the record back before you retry.
  - When: A column id is unknown, `columns` is not a list, or it is empty (`invalid_export_request`, "The selected export columns are invalid." or "Select at least one export column."). Fix: Use ids from the definitions operation, or omit `columns`.
  - When: `idempotency_key` is not a string or is longer than 100 characters (`invalid_export_request`, "The export request key is invalid."). Fix: Send a string of up to 100 characters, or leave it out.
  - When: An export of this type is already pending or generating for your team (`duplicate_report`, "There is already a report being generated. Please wait until the report is fully generated"). Fix: Poll the running export and download it instead, or wait until it finishes.
  - When: The `idempotency_key` was used for a different export, or by another member (`invalid_export_request`, "This request key was already used for a different export."). Fix: Use a new key for a new export.
- `429` Your team already has 3 exports pending or generating.
  - When: Three exports are already pending or generating (`export_capacity`). Fix: Wait for one to finish, then try again.
- `503` Exports are paused for a few minutes during a YoPrint update, or the export could not be queued (`export_queue_failed`, "The export could not be queued. Try again.").
  - When: Exports are paused during an update (`export_creation_paused`, with `Retry-After: 120`), even for a retry with the same key; or the export could not be queued (`export_queue_failed`, no `Retry-After`). Fix: When paused, wait the `Retry-After` seconds and send the request again. After a queue failure, send it again with the same `idempotency_key` to queue the same export.

## Notes

- The full flow: create the export, poll `GET /v1/api/store/{team_slug}/data_export/{id}` every few seconds until `status` is `ready` (or `failed`), then download the file with `GET /v1/api/store/{team_slug}/data_export/{id}/download` within 3 days.
- Retrying with the same `idempotency_key` returns the same export and starts nothing new while it is `pending`, `generating`, `ready`, or failed with `export_generation_failed` (send a new key to try that one again). After `export_queue_failed` or `export_attempt_expired`, the same key queues the same export again, and the one-per-type (422 `duplicate_report`) and three-per-team (429 `export_capacity`) limits apply.
- The file is CSV, UTF-8 with a byte order mark. Its first row holds the column ids, such as `order_id` (not the labels), in the order of `columns` or, without it, the default order. An export with no matching records has only that row.
- `progress.completed` stays 0 until a product export is ready.

## Related

- [Get a data export](https://developer.yoprint.com/reference/data-exports/v1-data-export-show.md): `GET /v1/api/store/{team_slug}/data_export/{id}`
- [Download a data export's file](https://developer.yoprint.com/reference/data-exports/v1-data-export-download.md): `GET /v1/api/store/{team_slug}/data_export/{id}/download`
- [List export types and their columns](https://developer.yoprint.com/reference/data-exports/v1-data-export-definitions.md): `GET /v1/api/store/{team_slug}/data_export/definitions`
- [Export all simple products to CSV](https://developer.yoprint.com/reference/data-exports/v1-data-export-export-simple-product-list.md): `POST /v1/api/store/{team_slug}/data_export/export_simple_product_list`
