API reference

Base URL: https://api.ecomsolo.com. All endpoints live under /v1 and authenticate with Authorization: Bearer esk_live_....

Data endpoints require the read_data scope; the export endpoints require exports — see Scopes.

GET /v1/account

Verify a key and see the account. Not plan-gated — it answers even when the API is not enabled, which makes it the right diagnostic.

Response

{
  "account_id": "acct_9f3c...",
  "account_name": "Acme Retail Group",
  "subscription_id": "sub_2a71...",
  "api_enabled": true,
  "store_count": 3,
  "scopes": ["read_data"]
}
FieldTypeMeaning
account_idstringThe account identifier.
account_namestringThe account's display name.
subscription_idstringThe account's subscription identifier.
api_enabledbooleanWhether the plan includes the API.
store_countnumberHow many stores the key can access.
scopesstring arrayThe scopes granted to the key.

GET /v1/stores

The stores the key can access.

Response — an array of:

FieldTypeMeaning
idstringStore identifier (use in store_ids).
namestringStore name (use in WHERE store IN (...)).
myshopify_domainstringThe store's myshopify.com domain.
currencystringThe store's currency, ISO code.
timezonestringThe store's IANA time zone.
statusstringactive or syncing.

GET /v1/datasets

The self-describing catalog of datasets and fields. See Datasets and fields.

Response — an array of datasets:

[
  {
    "name": "sales",
    "title": "Sales",
    "description": "Consolidated sales across all your stores.",
    "fields": [
      {
        "name": "total_sales",
        "title": "Total sales",
        "description": "...",
        "type": "money",
        "role": "metric",
        "default_aggregation": "sum"
      }
    ]
  }
]

Field type is one of string, number, money, date, boolean, percent. Field role is dimension or metric. default_aggregation is one of sum, avg, min, max, count, count_distinct, or null for dimensions.

POST /v1/query

Run a SoloQL query. See Querying with SoloQL.

Request body

FieldTypeMeaning
querystring, requiredThe SoloQL query string.
store_idsstring array, optionalNarrow to these store IDs; can only narrow within the key's scope, never widen.
time_zonestring, optionalIANA zone; defaults to the account's zone.

Response

{
  "columns": [
    { "name": "store", "title": "Store", "type": "string", "role": "dimension" }
  ],
  "rows": [
    { "store": "EU Shop", "total_sales": 48210.55 }
  ],
  "totals": { "total_sales": 120144.65 },
  "currency": "EUR",
  "time_zone": "Europe/Athens",
  "window": { "gte": "2026-06-24T00:00:00Z", "lt": "2026-07-24T00:00:00Z" }
}
FieldMeaning
columnsOne entry per column: name, title, type, role. Rows are keyed by name.
rowsThe result rows.
totalsPresent only with WITH TOTALS.
currencyISO currency the money columns were converted to.
time_zoneIANA zone the window resolved in.
windowResolved UTC range (gte, lt).

Send Accept: text/csv to get the same tabular result as CSV, with the column titles as the header row.

GET /v1/{dataset}

Fetch the records themselves, filtered and paged. Full guide: Fetching records.

dataset is one of orders, customers, products, draft_orders.

Query parameters

ParameterTypeMeaning
pagenumber, optional1-based page number. Default 1.
page_sizenumber, optionalRecords per page. Default 50, maximum 250.
fieldsstring, optionalComma-separated fields to return. Fewer fields means smaller responses and less personal data in transit.
store_idsstring, optionalComma-separated store IDs. Narrows within the key's scope, never widens.
created_at_mindate, optionalOnly records created at or after this time (ISO 8601, UTC).
created_at_maxdate, optionalOnly records created before this time (ISO 8601, UTC).
any field namestring, optionalEquality filter, e.g. financial_status=paid. Comma-separate for OR: financial_status=paid,refunded.

Response

{
  "data": [
    { "order_name": "#1001", "financial_status": "paid", "total_price": 129.5 }
  ],
  "page": 1,
  "page_size": 50,
  "has_more": true
}
FieldMeaning
dataThe records. Field names match the dataset's field names.
page / page_sizeEcho of the page requested.
has_moreWhether another page exists. There is no total count — loop until this is false.

info

Not every queryable field is a record field. Aggregate-only fields such as order_count exist so COUNT has something to count and have no meaning on a single record, so they are never returned and are rejected in fields. Use GET /v1/datasets for the field list, and Querying with SoloQL when you want the aggregate.

GET /v1/{dataset}/{id}

One record by id, returned directly rather than wrapped in data. Accepts fields.

An unknown id and an id belonging to a store the key cannot read both return 404 — deliberately indistinguishable, so the API never confirms the existence of data you are not entitled to.

Export endpoints

These four require the exports scope, not read_data. Full guide: Running exports.

GET /v1/exports

Saved export configurations. Each entry: id, name, entity_type, format, runnable_via_api, last_run_at, last_run_status, total_runs.

POST /v1/exports/{id}/run

Starts a run. 202 Accepted with job_id and status: "queued" — the work was accepted, not completed. The run still delivers to the destination configured on the export; API download is additive.

422 if the export's type has no queue topic and cannot be triggered through the API.

GET /v1/export-jobs/{id}

Job status.

FieldMeaning
statusqueued, running, completed or failed.
file_nameSet once the file exists.
errorThe failure reason when status is failed.

GET /v1/export-jobs/{id}/download

Streams the file with its real content type. 409 if the job has not produced a file yet — poll until completed first. 404 if the file has since been cleaned up (files are removed on re-sync or uninstall).

Error model

Errors use RFC 7807 problem+json, with Content-Type: application/problem+json:

{
  "type": "about:blank",
  "title": "Invalid query",
  "status": 400,
  "detail": "The query has one or more errors.",
  "errors": [
    { "line": 1, "column": 13, "message": "Unknown field 'totl_sales'", "severity": "error" }
  ]
}
StatusWhenNotes
400Invalid query — empty, or SoloQL with errors.For SoloQL errors the body includes errors, an array of positioned diagnostics (line, column, message, severity).
400Malformed request body.Title Invalid request. The body includes errors with a field and message per problem.
401Unauthorized.Missing, malformed, unknown, or revoked key.
403Forbidden."Insufficient scope" (key lacks read_data) or "Plan upgrade required" (plan does not include the API). GET /v1/account still works and reports api_enabled: false.
429Rate limit exceeded.Includes Retry-After (seconds) and X-RateLimit-Limit headers. See Rate limits.