Running exports

Queries and record endpoints answer questions and return pages. When you want everything — a full order history, a nightly warehouse load — that is an export, and these endpoints let you trigger one from code instead of clicking in the app.

Exports need the exports scope. A key with only read_data gets 403 here — triggering work and downloading files is a different permission from reading data.

How it works

An export of a large store takes minutes, which is longer than an HTTP request should wait. So you don't wait: you start a job, get an id back immediately, and check on it.

1. POST /v1/exports/{id}/run      → { "job_id": "...", "status": "queued" }
2. GET  /v1/export-jobs/{job_id}  → poll until "completed"
3. GET  /v1/export-jobs/{job_id}/download → the file

1. Find your exports

You run exports you have already set up in the app — the same configurations that power your scheduled exports, with their columns, filters and format already chosen.

curl -H "Authorization: Bearer $ECOMSOLO_API_KEY" \
  https://api.ecomsolo.com/v1/exports
{
  "data": [
    {
      "id": "exp_7f21...",
      "name": "Nightly orders",
      "entity_type": "Orders",
      "format": "csv",
      "runnable_via_api": true,
      "last_run_at": "2026-07-30T02:00:11Z",
      "last_run_status": "Success",
      "total_runs": 214
    }
  ]
}

info

runnable_via_api tells you whether that configuration can be triggered here. A few export types run only from the app; trying to run one returns 422 naming the type rather than silently doing nothing.

2. Start a run

curl -X POST -H "Authorization: Bearer $ECOMSOLO_API_KEY" \
  https://api.ecomsolo.com/v1/exports/exp_7f21.../run
{ "job_id": "job_a93c...", "status": "queued" }

202 Accepted — the run has been accepted, not finished. Keep the job_id.

info

An API-triggered run still delivers to the destination you configured — email, FTP, Google Drive or Sheets. Downloading through the API is additive, so automating the trigger never stops colleagues receiving the file where they expect it.

3. Poll until it's done

curl -H "Authorization: Bearer $ECOMSOLO_API_KEY" \
  https://api.ecomsolo.com/v1/export-jobs/job_a93c...
{
  "job_id": "job_a93c...",
  "status": "running",
  "name": "Nightly orders",
  "entity_type": "Orders",
  "created_at": "2026-07-31T02:00:03Z",
  "file_name": null,
  "error": null
}
statusMeaning
queuedAccepted, not started yet.
runningBeing produced.
completedThe file is ready to download.
failedIt did not finish — see error.

Poll every 15–30 seconds. Most exports finish in under a minute; very large ones take several. There is no callback or webhook — poll until completed or failed.

4. Download

curl -H "Authorization: Bearer $ECOMSOLO_API_KEY" \
  -o orders.csv \
  https://api.ecomsolo.com/v1/export-jobs/job_a93c.../download

Returns the file with its real content type (text/csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet for Excel, application/zip for bundles).

Asking to download before the job finishes returns 409 — the job exists, it just isn't ready. Poll first.

A complete nightly pull

#!/usr/bin/env bash
set -euo pipefail
API="https://api.ecomsolo.com"
AUTH="Authorization: Bearer $ECOMSOLO_API_KEY"

JOB=$(curl -sS -X POST -H "$AUTH" "$API/v1/exports/$EXPORT_ID/run" \
      | jq -r .job_id)

while :; do
  STATUS=$(curl -sS -H "$AUTH" "$API/v1/export-jobs/$JOB" | jq -r .status)
  case "$STATUS" in
    completed) break ;;
    failed)    curl -sS -H "$AUTH" "$API/v1/export-jobs/$JOB" | jq -r .error >&2; exit 1 ;;
    *)         sleep 20 ;;
  esac
done

curl -sS -H "$AUTH" -o orders.csv "$API/v1/export-jobs/$JOB/download"

Notes worth knowing

Files don't live forever. Export files are cleaned up when a store re-syncs or the app is uninstalled. Download soon after completion rather than relying on an old job_id — if the file has gone, run the export again.

Each run is its own job. Running the same configuration twice gives two job_ids and two files. The configuration is the recipe; the job is one cooking of it.

Rate limits apply to these endpoints like any other — see Rate limits. Starting an export costs one request; so does each poll, which is the other reason not to poll every second.

Which should I use?

You wantUse
A total, a trend, a breakdownPOST /v1/query
Some records, filteredGET /v1/orders
Everything, as a filePOST /v1/exports/{id}/run