Reports
The back-in-stock reports resource mirrors the preorder reports machinery: every report has a JSON read and a sibling async CSV export. Reads run against read replicas.
Path prefix: /back_in_stock/reports
Report manifest
GET /back_in_stock/reportsReturns the catalog of available reports — { "reports": [{ "name", "description", "aliases", "url" }] } — generated from the live registry so it can't drift from the actual report actions.
Shared filters
All reads accept the same filter set:
| Param | Type | Notes |
|---|---|---|
from, to | ISO 8601 date | Window — defaults to the last 30 days |
variant_id | integer | Single Shopify variant id |
product_id | integer | Single Shopify product id |
channel | email / sms / push | |
granularity | day / week / month | Time-series reports only; default day |
Every response echoes the resolved window at the top: "window": { "from": "...", "to": "..." }.
Reads
| Method | Path | Description |
|---|---|---|
GET | /back_in_stock/reports/summary | High-level overview of back-in-stock activity over a date window. |
GET | /back_in_stock/reports/signups | Time-series of signups created (day/week/month). |
GET | /back_in_stock/reports/notifications | Time-series of notifications sent, split by channel. |
GET | /back_in_stock/reports/conversions | Time-series of orders attributed to alerts and the revenue recovered. |
GET | /back_in_stock/reports/products_in_demand | Ranked variants by waitlist demand. |
Summary
Totals for the window — waitlist size, sends, and what they recovered:
{
"window": { "from": "...", "to": "..." },
"currency": "USD",
"totals": {
"signups": 420,
"pending": 180,
"notified": 240,
"conversions": 96,
"conversion_rate": 0.4,
"recovered_revenue": "4812.50"
}
}conversion_rate is the share of notified signups that converted to an attributed order. Conversions are order attributions at medium/high confidence.
Signups
Series of signups created per bucket, with the pending/notified split:
{
"window": { "...": "..." },
"granularity": "day",
"series": [
{ "date": "2026-06-01", "signups": 24, "pending": 10, "notified": 14 }
]
}Notifications
Series of sends per bucket, split by channel, with blocked counts:
{
"window": { "...": "..." },
"granularity": "day",
"series": [
{ "date": "2026-06-01", "sent": 120, "blocked": 4, "email": 100, "sms": 15, "push": 5 }
]
}This is the aggregate view. For the row-level send log — who got each notification, and why a blocked send was stopped — use the read-only notifications resource at GET /back_in_stock/notifications.
Conversions
Series of attributed orders and recovered revenue per bucket:
{
"window": { "...": "..." },
"granularity": "day",
"currency": "USD",
"series": [
{ "date": "2026-06-01", "orders": 8, "recovered_revenue": "401.00" }
]
}Products in demand
Ranked variants by demand — the v2 replacement for v1 products_in_demand, and the answer to "what should I restock first?".
| Param | Notes |
|---|---|
sort_by | pending (default) / total / last_requested_at |
direction | desc (default) / asc |
page, per_page | per_page default 50, max 500 |
{
"window": { "...": "..." },
"sort_by": "pending",
"direction": "desc",
"rows": [
{
"shopify_variant_id": 123,
"shopify_product_id": 456,
"variant_title": "Small / Blue",
"product_title": "Classic Tee",
"pending": 42,
"total": 65,
"last_requested_at": "2026-06-09T08:30:00Z"
}
],
"meta": { "total_count": 1, "page": 1, "per_page": 50, "total_pages": 1 }
}CSV exports
Every read has a sibling */export action:
| Method | Path |
|---|---|
POST | /back_in_stock/reports/summary/export |
POST | /back_in_stock/reports/signups/export |
POST | /back_in_stock/reports/notifications/export |
POST | /back_in_stock/reports/conversions/export |
POST | /back_in_stock/reports/products_in_demand/export |
Exports accept the same filters as the corresponding read (plus sort_by, direction, page, per_page where the read supports them). They submit a job and return 202 Accepted:
{
"job_id": "exp_1a2b3c4d5e6f7a8b",
"status_url": "/api/v2/external/back_in_stock/reports/exports/exp_1a2b3c4d5e6f7a8b"
}Polling
GET /back_in_stock/reports/exports/:job_idstatus is one of running, ready, failed, cancelled. When ready, the response includes the download URL:
{
"job_id": "exp_1a2b3c4d5e6f7a8b",
"status": "ready",
"download_url": "https://stoq-exports.s3.amazonaws.com/...",
"file_name": "back_in_stock_signups_2026-06-10.csv",
"row_count": 420,
"progress": 100
}An unknown job_id returns 404. The polling pattern is the same one used across v2 — see Bulk & Async Jobs.
Example: signups by week, then export
curl 'https://app.stoqapp.com/api/v2/external/back_in_stock/reports/signups?from=2026-03-01&to=2026-06-01&granularity=week' \
-H "X-Auth-Token: $STOQ_API_KEY"curl -X POST 'https://app.stoqapp.com/api/v2/external/back_in_stock/reports/signups/export' \
-H "X-Auth-Token: $STOQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"2026-03-01","to":"2026-06-01","granularity":"week"}'# Poll until status: "ready"
curl 'https://app.stoqapp.com/api/v2/external/back_in_stock/reports/exports/exp_1a2b3c4d5e6f7a8b' \
-H "X-Auth-Token: $STOQ_API_KEY"