Two endpoints to see what your store has consumed and project what you'll need next month.
GET /v1/usage
Current calendar month, grouped by endpoint type.
Auth: platform key with usage:read.
Response 200
{
"data": {
"period": "2026-04",
"tier": "enterprise",
"usage": {
"request": { "total": 31, "billable": 0 },
"signup": { "total": 3, "billable": 2 }
},
"limits": {
"requests_per_month": -1,
"signups_per_month": -1,
"webhooks_per_month": -1,
"requests_per_minute": 600
}
}
}
Field reference
Field | Meaning |
| Always |
| Your current rate-limit tier — always |
| All requests in that group, including duplicates / rejections. |
| Only counted requests (e.g. duplicate signups don't bill). |
| The effective limits — |
usage is sparse — only groups with recorded activity this calendar month appear (in practice request, plus signup / event for public-key traffic). If the store has no usage at all, usage serialises as an empty JSON array [], not an object. Default missing groups to zero client-side and tolerate []; a strictly-typed deserialiser will otherwise fail.
Endpoint groups
Group | What counts |
| Every call that passed key authentication, counted before the request is processed — so 4xx/5xx responses and scope rejections count too. Always |
| Each |
| Each |
| Reserved. Outbound deliveries are not metered at v1, so this group never appears in the response. |
GET /v1/usage/history
Hourly rollups over a date range — useful for charts and trend analysis.
Auth: platform key with usage:read.
Query parameters
Param | Type | Default | Notes |
| ISO date | 7 days ago | Inclusive |
| ISO date | now | Exclusive |
Range cap: 90 days. from is inclusive and to is exclusive, and both are floored to the top of the hour for the query — the from / to echoed in the response are your inputs as parsed, unfloored. Common date and date-time formats are accepted (2026-04-01, 2026-04-01T12:00:00Z, -7 days, …); an unparseable value or to earlier than from returns 400 bad_request ("from/to must be valid date strings, to >= from"), and a range over 90 days returns 400 ("range too large (max 90 days)").
period_hour buckets are whole hours, stamped in Africa/Algiers time (UTC+01:00) at the moment each call is counted.
Errors
HTTP | Code | Cause |
400 |
| "from/to must be valid date strings, to >= from" |
400 |
| "range too large (max 90 days)" |
403 |
| "Missing scope: usage:read" |
usage:read is one of the few scopes v1 actually enforces. It is granted by default on every minted platform key, so it only bites keys that support issued with a reduced scope set.
Request
curl 'https://api.dzbuild.app/v1/usage/history?from=2026-04-01&to=2026-05-01' \ -H "Authorization: Bearer $DZ_KEY"
Response 200
{
"data": {
"from": "2026-04-01T00:00:00+01:00",
"to": "2026-05-01T00:00:00+01:00",
"rows": [
{ "period_hour": "2026-04-30 19:00:00", "endpoint_group": "request", "count": 26, "billable_count": 0 },
{ "period_hour": "2026-04-30 20:00:00", "endpoint_group": "request", "count": 5, "billable_count": 0 },
{ "period_hour": "2026-04-30 20:00:00", "endpoint_group": "signup", "count": 3, "billable_count": 2 }
]
}
}
Rows are returned in ascending period_hour order. Hours with zero usage in any group are omitted (sparse).
Plotting tips
Daily aggregates: group rows by the first 10 characters of
period_hour(the date prefix).Stacked area: group by
endpoint_group, then by hour for the X-axis.Quota burn rate: divide
signup.billable_countcumulative by the elapsed fraction of the month, project to month-end.
A simple Python example:
import collections, datetime, requests, osr = requests.get('https://api.dzbuild.app/v1/usage/history',
params={'from': '2026-04-01', 'to': '2026-05-01'},
headers={'Authorization': f"Bearer {os.environ['DZ_KEY']}"})
rows = r.json()['data']['rows']by_day = collections.defaultdict(lambda: collections.Counter())
for row in rows:
day = row['period_hour'][:10]
by_day[day][row['endpoint_group']] += row['count']for day, counts in sorted(by_day.items()):
print(day, dict(counts))