Export Scores
Queue a bulk CSV export of your organization's PDP or image scores, poll until it completes, and download the artifact.
The export endpoints let you pull your organization's scores in bulk as a CSV — every PDP, or every image, that Vizit has scored for you. Unlike the per-product ingestion endpoints, an export is a single asynchronous job: you POST to queue it, poll until it's COMPLETED, then download the file. This guide walks the full loop, the filters you can apply, retention and concurrency behavior, and the error catalog.
Prerequisites
Before any of the requests below will succeed, make sure you have:
- Client credentials provisioned by Vizit for your organization and target environment (production, or
dev1–dev5). - A Bearer token obtained via the OAuth 2.0 client-credentials flow described in Authentication. Cache it until expiry — tokens are valid for 24 hours.
- A request ID strategy. Send
X-Request-Idon each request to correlate your logs with Vizit's. If you omit it, Vizit generates one and echoes it on the response.
All examples below assume $BASE_URL is the environment-appropriate hostname (e.g., https://ext.vizit.com) and $TOKEN is your current access token.
The export lifecycle
An export moves through four states:
status | Meaning | download_url |
|---|---|---|
PENDING | Accepted and queued. | null |
PROCESSING | Vizit is building the CSV. | null |
COMPLETED | The artifact is ready (within its 7-day retention). | populated |
ERROR | The job failed terminally; see error_code. | null |
The flow is always POST → poll GET → GET download. Exports complete asynchronously, so clients poll the status URL until status reaches COMPLETED (or ERROR).
1. Create the export
curl -X POST "$BASE_URL/v1/exports" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"subject": "pdps",
"filters": {
"retailer_region": "amazon_us",
"last_refreshed_after": "2026-01-01T00:00:00Z"
}
}'Request body:
| Field | Type | Required | Notes |
|---|---|---|---|
subject | string | Yes | "pdps" or "images". Any other value returns 400 INVALID_SUBJECT. |
filters | object | No | All keys optional. Unknown keys return 400 INVALID_FILTER. |
filters.portfolio_id | uuid | No | Restrict to one portfolio. A portfolio outside your org returns 404 PORTFOLIO_NOT_FOUND. |
filters.retailer_region | string | No | e.g. amazon_us. Unsupported values return 400 INVALID_RETAILER_REGION. |
filters.last_refreshed_after | string | No | RFC 3339 / ISO 8601 timestamp; includes rows refreshed since. Malformed values return 400 INVALID_TIMESTAMP. |
Successful response (202 Accepted):
{
"export_id": "496c1503-2be9-4908-9c46-ca97612a62ea",
"subject": "pdps",
"status": "PENDING",
"status_url": "/v1/exports/496c1503-2be9-4908-9c46-ca97612a62ea",
"requested_at": "2026-06-03T19:41:10Z"
}status_url is the path you poll next.
One export at a time per organization
An organization may have only one export PENDING or PROCESSING at a time. Submitting a second while one is in flight returns 409 EXPORT_CONCURRENCY_LIMIT. Wait for the active export to finish (poll its status URL), then submit the next.
2. Poll for completion
curl "$BASE_URL/v1/exports/496c1503-2be9-4908-9c46-ca97612a62ea" \
-H "Authorization: Bearer $TOKEN"While the job runs, status is PENDING then PROCESSING and download_url is null. Once it lands:
{
"export_id": "496c1503-2be9-4908-9c46-ca97612a62ea",
"subject": "pdps",
"status": "COMPLETED",
"requested_at": "2026-06-03T19:41:10Z",
"completed_at": "2026-06-03T19:41:11Z",
"row_count": 2291,
"byte_size": 412204,
"download_url": "/v1/exports/496c1503-2be9-4908-9c46-ca97612a62ea/download",
"download_url_expires_at": "2026-06-10T19:41:11Z",
"expires_at": "2026-06-10T19:41:11Z",
"error_code": null
}A polling interval of 5–10 seconds is plenty — exports complete quickly relative to the scoring pipeline.
3. Download the artifact
curl "$BASE_URL/v1/exports/496c1503-2be9-4908-9c46-ca97612a62ea/download" \
-H "Authorization: Bearer $TOKEN" \
-o export.csvThe download endpoint streams text/csv with a Content-Disposition: attachment filename. It uses the same Bearer token as every other request — there's no presigned URL and no separate credential to manage.
The CSV always starts with a stable header row, then one row per record:
subject: "pdps"— one row per PDP: identifiers (pdp_id,portfolio_id,retailer_region,item_id),name,brand,category_id,category, the listing scores and penalties, content-mix fields,score_status, andcreated_at/updated_at/last_refreshed.subject: "images"— one row per image: identifiers,image_type(hero/carousel),image_url,position,vizit_score,raw_score, plus hero-only columns (hero_score,mobile_*) that are blank for carousel rows, classification fields, and timestamps.
Retention: 7 days, then the file is gone
The artifact lives for 7 days from completion (expires_at = completed_at + 7 days). The download_url stays valid for exactly that window — there is no separate URL-level TTL; the URL works as long as the artifact does. After expiry, GET on the status URL returns status: COMPLETED with a null download_url and error_code: EXPORT_EXPIRED, and the download endpoint returns 404 EXPORT_EXPIRED. Re-submit the export to regenerate it.
Error cheat sheet
Every non-2xx response follows the standard error shape: error_code, detail, status_code, request_id. Branch on error_code, never on detail.
Validation errors (400)
error_code | What it means | Likely caller action |
|---|---|---|
INVALID_SUBJECT | subject isn't pdps or images. | Send a valid subject. |
INVALID_FILTER | filters contains an unknown key. | Remove the unrecognized key. |
INVALID_RETAILER_REGION | filters.retailer_region isn't a supported region. | Use a documented retailer-region key (e.g. amazon_us). |
INVALID_TIMESTAMP | filters.last_refreshed_after isn't a valid RFC 3339 timestamp. | Send an ISO 8601 / RFC 3339 value. |
Resource & state errors (404 / 409)
error_code | Status | When | What to do |
|---|---|---|---|
PORTFOLIO_NOT_FOUND | 404 | filters.portfolio_id isn't a portfolio in your org. | Use a portfolio your organization owns. (Cross-org IDs return 404, not 403, so existence isn't leaked.) |
EXPORT_CONCURRENCY_LIMIT | 409 | An export is already PENDING/PROCESSING for your org. | Wait for it to finish, then submit again. |
EXPORT_NOT_FOUND | 404 | The export_id doesn't exist in your org. | Check the id; cross-org ids are indistinguishable from missing. |
EXPORT_NOT_READY | 404 | You called /download before the export reached COMPLETED. | Poll the status URL until status is COMPLETED. |
EXPORT_EXPIRED | 404 | The artifact is past its 7-day retention (or was purged). | Re-submit the export to regenerate it. |
Terminal failure
error_code | Where | What to do |
|---|---|---|
EXPORT_FAILED | status: ERROR on the status response | The job hit an unrecoverable error. Re-submit; if it persists, contact Vizit with the export_id. |
Auth and rate-limit errors
| Status | Action |
|---|---|
401 Unauthorized | Request a fresh access token and retry once. See Authentication. |
403 Forbidden | The token is valid but lacks permission. Contact Vizit. |
429 Too Many Requests | Back off for the Retry-After seconds, then resume. |
Rate limits and retries
- Per-organization rate limits are enforced at the API gateway; the export concurrency cap (one in-flight job per org) is separate and enforced at submission time.
- Retry
429and5xxresponses with exponential backoff (start ~5s, double each retry, cap ~60s). - Do not retry
4xxresponses other than429until you've fixed the request — the same input fails the same way.
What's next
- The full operation contracts are in the API Reference:
- For credentials, token acquisition, and security guidance: Authentication.
- For the cross-cutting error response format: Errors.