Analytics
GET /v1/analytics returns the figures the dashboard home page shows for a period: ten KPIs, each with its value for a comparison period and the change, and eight chart series. Use it for client reports or a BI export instead of rebuilding the numbers from GET /v1/orders. The API and the dashboard share the same definitions and the same cache, so for the same period they show the same numbers. What each figure means for the merchant is explained on the Analytics page of the merchant docs.
The API always counts all traffic: store pages and landing pages together. The dashboard's traffic filter (landing pages only, or the store only) has no equivalent here.
Before you start
- The key needs
analytics:read. Keys created from the dashboard (Settings → API,/dashboard/api) carry it since v1.7. Scopes are frozen when a key is created, so an older key answers403 forbiddenwith "Missing scope: analytics:read": create a new key from the dashboard. A key created throughPOST /v1/keysonly gets the scopes held by the key that created it. - Personal keys need a store on an active Enterprise plan. An installed app's token is not bound to Enterprise, but it needs
analytics:readamong the scopes the merchant approved. See Introduction. - Revenue, profit and average order value, and the
revenuevalues inside the charts, are sent only when the key belongs to the store owner's account. Otherwise those fields are missing from the answer: they are not set to0ornull.
| Scope | Description |
|---|---|
analytics:read | Read store analytics and KPIs. Included in merchant keys created from v1.7 on; an older key needs a new key. |
GET /v1/analytics
The report for one period.
Auth: platform key with analytics:read.
Query parameters
| Param | Type | Default | Notes |
|---|---|---|---|
range | string | this_month | today, yesterday, 7d, 30d, this_month, last_month, this_year or custom. Any other value returns 400 bad_request. |
from | YYYY-MM-DD | none | First day. Required with range=custom, ignored otherwise. |
to | YYYY-MM-DD | none | Last day. Required with range=custom, ignored otherwise. Never before from, at most 365 days after it. |
Periods
range | Period | Compared with | group_by |
|---|---|---|---|
today | Today | Yesterday | hour |
yesterday | Yesterday | The day before | hour |
7d | Today and the 6 days before | The 7 days before those | day |
30d | Today and the 29 days before | The 30 days before those | day |
this_month | The 1st to the last day of the current month | The whole previous month | day |
last_month | The whole previous month | The month before it | day |
this_year | 1 January to 31 December of the current year | The whole previous year | month |
custom | from to to, both days included | The same number of days just before from | hour, day or month |
For custom, group_by is hour when to is from or the next day, day when to is at most 90 days after from, and month beyond that. this_month and this_year run to the end of the month or the year, so they compare the days so far with a whole previous month or year.
The report is cached for 120 seconds per store and period, and the dashboard reads the same cache. A call can return figures up to two minutes old, and calls inside that window get the same numbers.
Request
curl 'https://api.dzbuild.app/v1/analytics?range=7d' \
-H "Authorization: Bearer $DZ_KEY"
A custom period:
curl 'https://api.dzbuild.app/v1/analytics?range=custom&from=2026-09-01&to=2026-09-30' \
-H "Authorization: Bearer $DZ_KEY"
Response 200
The answer for range=7d on 6 October 2026, to a key created by the store owner. The time series and the lists are cut to their first entries.
{
"data": {
"period": {
"from": "2026-09-30 00:00:00",
"to": "2026-10-06 23:59:59",
"prev_from": "2026-09-23 00:00:00",
"prev_to": "2026-09-29 23:59:59",
"label": "7d",
"group_by": "day"
},
"kpis": {
"total_orders": { "value": 48, "change": 20, "previous": 40 },
"delivered_orders": { "value": 31, "change": 7, "previous": 29 },
"cancelled_orders": { "value": 6, "change": -25, "previous": 8 },
"total_revenue": { "value": 186000, "change": 8, "previous": 172000 },
"total_profit": { "value": 61500, "change": 6, "previous": 58000 },
"avg_order_value": { "value": 4350, "change": -1, "previous": 4400 },
"total_visitors": { "value": 1520, "change": 17, "previous": 1300 },
"page_views": { "value": 4810, "change": 17, "previous": 4100 },
"conversion_rate": { "value": 3.2, "change": 3, "previous": 3.1 },
"new_customers": { "value": 41, "change": 17, "previous": 35 }
},
"charts": {
"revenue_over_time": [
{ "label": "09/30", "orders": 7, "revenue": 24500 },
{ "label": "10/01", "orders": 5, "revenue": 18000 }
],
"orders_by_hour": [
{ "hour": "00:00", "orders": 0, "impressions": 35 },
{ "hour": "01:00", "orders": 1, "impressions": 22 }
],
"orders_by_status": {
"pending": 5,
"confirmed": 4,
"processing": 2,
"shipped": 9,
"delivered": 20,
"cancelled": 6,
"returned": 2
},
"top_products": [
{
"id": 12,
"name": "Classic watch",
"image": "https://cdn.dzbuild.app/uploads/products/123/123_1700000000_example.webp",
"qty_sold": 14,
"revenue": 49000,
"views": 380
}
],
"visitors_over_time": [
{ "label": "09/30", "page_views": 690, "unique_visitors": 215, "orders": 7 },
{ "label": "10/01", "page_views": 702, "unique_visitors": 230, "orders": 5 }
],
"devices": {
"desktop": { "count": 510, "percent": 11 },
"mobile": { "count": 4180, "percent": 87 },
"tablet": { "count": 120, "percent": 2 }
},
"traffic_sources": [
{ "source": "facebook", "count": 2900, "percent": 60 },
{ "source": "direct", "count": 1210, "percent": 25 },
{ "source": "tiktok", "count": 700, "percent": 15 }
],
"top_wilayas": [
{ "wilaya": "الجزائر", "orders": 9, "revenue": 41000 },
{ "wilaya": "وهران", "orders": 6, "revenue": 27500 }
]
}
}
}
The period
| Field | Meaning |
|---|---|
from, to | Start and end of the period, YYYY-MM-DD HH:MM:SS, Algiers time. |
prev_from, prev_to | Start and end of the comparison period. |
label | The range you asked for. |
group_by | Bucket size of revenue_over_time and visitors_over_time: hour, day or month. |
KPIs
Each KPI has three numbers: value for the period, previous for the comparison period, and change, the percent change rounded to a whole number. When previous is 0 or below, change is 100 if value is above 0, and 0 otherwise. Amounts are rounded to whole numbers.
| KPI | What it counts |
|---|---|
total_orders | Orders created in the period, whatever their status. |
delivered_orders | Orders delivered in the period, counted on the day they were delivered. |
cancelled_orders | Orders created in the period that are now cancelled. |
total_revenue | Owner only. Subtotal minus discount of the orders delivered in the period. Shipping and payment fees are not included. |
total_profit | Owner only. total_revenue minus quantity × cost price of the items in those orders. A product with no cost price counts as zero cost. |
avg_order_value | Owner only. Average order total of the orders created in the period, cancelled and returned orders left out. |
total_visitors | Distinct visitors in the period, store pages and landing pages together. |
page_views | Page views in the period, store pages and landing pages together. |
conversion_rate | total_orders ÷ total_visitors × 100, with one decimal. 0 when there were no visitors. |
new_customers | Customer records created in the period. |
These KPIs come from different sets of orders, so they do not add up: total_revenue ÷ total_orders is not avg_order_value.
Charts
| Chart | Content |
|---|---|
revenue_over_time | One entry per bucket: label, the orders created in the bucket and, for the owner only, revenue, the order totals of those orders that are now delivered. It is not total_revenue, which counts by delivery day and leaves shipping out. |
orders_by_hour | Always 24 entries, 00:00 to 23:00: the orders created and the page views (impressions) at that hour of the day, over the whole period. |
orders_by_status | Orders created in the period by current status: pending, confirmed, processing, shipped, delivered, cancelled and returned. |
top_products | Up to 10 products by quantity sold in the orders created in the period, cancelled and returned orders left out. Each has id, name, image (main image URL or null), qty_sold, views (distinct visitors on the product in the period) and, for the owner only, revenue (quantity × the price on the order). |
visitors_over_time | One entry per bucket: label, page_views, unique_visitors and orders. A visitor who returns on another day counts in both buckets, so the unique_visitors values can add up to more than total_visitors. |
devices | Page views by device: desktop, mobile and tablet keys, each with count and percent. A device with no views has no key, and the field is an empty array [] when the period had no visits. |
traffic_sources | Up to 6 sources by page views, busiest first, each with source, count and percent. percent is the share among the listed sources. |
top_wilayas | Up to 10 wilayas by number of orders created in the period, all statuses: wilaya (the Arabic name, غير محدد for orders with no wilaya), orders and, for the owner only, revenue, the order totals of those orders whatever their status. |
Labels follow group_by: 00:00 to 23:00 for hour (24 entries; a two-day custom period adds both days into the same hours), MM/DD for day, and the English month and year for month, such as Sep 2026. Days after today and months after the current one are left out.
The platform records source as direct, facebook, instagram, tiktok, google, youtube, twitter, snapchat, telegram or other. A visit whose link carries utm_source is classed by that tag (fb counts as facebook, ig as instagram, x as twitter, any tag not in the list as other); a visit without it is classed by the site it came from.
Errors
| HTTP | Code | Cause |
|---|---|---|
| 400 | bad_request | range is not one of the eight values ("Invalid range. Allowed: ..."), or a custom period whose from or to is missing or not YYYY-MM-DD, or whose to is before from or more than 365 days after it ("Invalid date range ..."). |
| 401 | unauthorized | Bad or missing key. |
| 402 | quota_exceeded | The store's monthly request quota is used up. See Rate limits. |
| 403 | forbidden | "Missing scope: analytics:read", or a personal key whose store is not on an active Enterprise plan ("API access requires an active Enterprise plan"). |
| 429 | rate_limited | The store's per-minute cap, shared by all of its keys, or the per-install cap of an installed app's token. Wait for Retry-After. See Rate limits. |
Known limits
- Month ends. When today's day number does not exist in the previous month, as on 31 October,
range=last_monthreturns the current month andrange=this_monthis compared with the current month itself.last_monthis also compared with itself when the day does not exist two months back, as on 30 April. On those days, ask for the dates you need withrange=custom.