Vizit Public APIAPI
Guides

Export Scores

Queue a bulk CSV export of your organization's PDP or image scores, poll until it completes, and download the artifact.

Guides

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 dev1dev5).
  • 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-Id on 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:

statusMeaningdownload_url
PENDINGAccepted and queued.null
PROCESSINGVizit is building the CSV.null
COMPLETEDThe artifact is ready (within its 7-day retention).populated
ERRORThe job failed terminally; see error_code.null

The flow is always POST → poll GETGET 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:

FieldTypeRequiredNotes
subjectstringYes"pdps" or "images". Any other value returns 400 INVALID_SUBJECT.
filtersobjectNoAll keys optional. Unknown keys return 400 INVALID_FILTER.
filters.portfolio_iduuidNoRestrict to one portfolio. A portfolio outside your org returns 404 PORTFOLIO_NOT_FOUND.
filters.retailer_regionstringNoe.g. amazon_us. Unsupported values return 400 INVALID_RETAILER_REGION.
filters.last_refreshed_afterstringNoRFC 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.csv

The 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, and created_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_codeWhat it meansLikely caller action
INVALID_SUBJECTsubject isn't pdps or images.Send a valid subject.
INVALID_FILTERfilters contains an unknown key.Remove the unrecognized key.
INVALID_RETAILER_REGIONfilters.retailer_region isn't a supported region.Use a documented retailer-region key (e.g. amazon_us).
INVALID_TIMESTAMPfilters.last_refreshed_after isn't a valid RFC 3339 timestamp.Send an ISO 8601 / RFC 3339 value.

Resource & state errors (404 / 409)

error_codeStatusWhenWhat to do
PORTFOLIO_NOT_FOUND404filters.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_LIMIT409An export is already PENDING/PROCESSING for your org.Wait for it to finish, then submit again.
EXPORT_NOT_FOUND404The export_id doesn't exist in your org.Check the id; cross-org ids are indistinguishable from missing.
EXPORT_NOT_READY404You called /download before the export reached COMPLETED.Poll the status URL until status is COMPLETED.
EXPORT_EXPIRED404The artifact is past its 7-day retention (or was purged).Re-submit the export to regenerate it.

Terminal failure

error_codeWhereWhat to do
EXPORT_FAILEDstatus: ERROR on the status responseThe job hit an unrecoverable error. Re-submit; if it persists, contact Vizit with the export_id.

Auth and rate-limit errors

StatusAction
401 UnauthorizedRequest a fresh access token and retry once. See Authentication.
403 ForbiddenThe token is valid but lacks permission. Contact Vizit.
429 Too Many RequestsBack 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 429 and 5xx responses with exponential backoff (start ~5s, double each retry, cap ~60s).
  • Do not retry 4xx responses other than 429 until you've fixed the request — the same input fails the same way.

What's next

On this page