Volume Trends (report)
/api/v1/reports/volume-trendsRequest
/api/v1/reports/volume-trendscurl -X GET 'https://app.mailyte.com/api/v1/reports/volume-trends' \
-H 'Authorization: Bearer mk_live_YOUR_API_KEY'const response = await fetch('https://app.mailyte.com/api/v1/reports/volume-trends', {
method: 'GET',
headers: {
Authorization: 'Bearer mk_live_YOUR_API_KEY',
},
});
const { data } = await response.json();import requests
response = requests.get(
"https://app.mailyte.com/api/v1/reports/volume-trends",
headers={"Authorization": "Bearer mk_live_YOUR_API_KEY"},
)
data = response.json()["data"]<?php
$response = Http::withToken('mk_live_YOUR_API_KEY')
->get('https://app.mailyte.com/api/v1/reports/volume-trends');
$data = $response->json('data');require "net/http"
require "json"
uri = URI("https://app.mailyte.com/api/v1/reports/volume-trends")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer mk_live_YOUR_API_KEY"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }Response
Success.
dataobjectobjectstringvolume_reportgenerated_atstringWhen this response was computed, ISO 8601 UTC. Every report answers it, so "is this figure stale?" never depends on which report you asked.
periodobjectThe window this report actually covers, which is not always the window you asked for: a `date_range` outside `day`, `week`, `month` and `quarter` is silently replaced with `month`, so a request for `year` returns thirty days and this object is the only place that says so. Null when the report covers no window -- a stored result read back from `/reports/history` keeps no record of the window it was built over, and inventing one from your current query would describe the query rather than the data.
start_atstringend_atstringgranularitystringdayThe width of one `series` bucket. Always `day`: the `granularity` query parameter is accepted and ignored.
totalsobjectThe top-line counts. `average_daily_volume` and `peak_volume` are gone: both were one line of caller code over `series`, and both were computed from `events`, which is not a message count.
eventsintegerEvery `delivery_events` row in the window, tracking included -- opens, clicks and unsubscribes as well as sends. NOT a message count: it is roughly the message count multiplied by how much the recipients engaged. Null means the figure is unavailable, never that it is zero.
sentintegerIn THIS report only, `sent` is `delivered + bounced` -- the messages that reached a receiving server and got a verdict. `/reports/email-delivery` publishes `sent` as five event types (deferrals, rejections and drops as well), so the same name returns a different and smaller number here. Do not compare the two directly. Recorded as a defect in plans/13-public-api-contract/converted/mailserver.md; the counts are published as the service computes them rather than quietly reconciled. Summed from `series`, so the two can never drift apart.
receivedintegerInbound mail (`email.inbound`). Summed from `series`.
seriesarray<object>The same counts bucketed over the period, oldest first. Rows are SPARSE: a bucket in which nothing happened has no row at all, rather than a row of zeros, so do not index this array by day offset.
start_atstringThe instant this bucket opens, midnight UTC. Replaces the bare `2026-09-17` these rows used to carry, which named no timezone. NULL MEANS UNKNOWN -- we could not read this bucket's boundary -- and never that the bucket has no start or is empty. A LIVE report cannot produce it: the rows are grouped by `delivery_events.created_at`, which is NOT NULL, so every bucket is keyed by a real date. It is reachable only through `GET /reports/history`, where the row is JSON written by whichever version of the report ran at the time and re-rendered through this same resource WITHOUT being re-validated; a stored row carrying no date becomes null here rather than being silently dated today. Drop such a row or read the report live.
eventsintegerEvery `delivery_events` row in the window, tracking included -- opens, clicks and unsubscribes as well as sends. NOT a message count: it is roughly the message count multiplied by how much the recipients engaged. Null means the figure is unavailable, never that it is zero.
sentintegerIn THIS report only, `sent` is `delivered + bounced` -- the messages that reached a receiving server and got a verdict. `/reports/email-delivery` publishes `sent` as five event types (deferrals, rejections and drops as well), so the same name returns a different and smaller number here. Do not compare the two directly. Recorded as a defect in plans/13-public-api-contract/converted/mailserver.md; the counts are published as the service computes them rather than quietly reconciled. Null means the figure is unavailable, never that it is zero.
receivedintegerInbound mail (`email.inbound`). Null means the figure is unavailable, never that it is zero.
distributionsobjectHistograms, not time series, which is why they are not `series`. TWO CAVEATS, both unchanged behaviour: the buckets are SPARSE -- an hour or a weekday with no events has no row rather than a zero -- and they are cut on the SERVER's application timezone, not yours and not UTC, so a bucket label is only meaningful once you know that timezone. The `percentage` each row used to carry is gone; it duplicated `events` over `totals.events`.
hour_of_dayarray<object>bucketstring`"00"` to `"23"`.
eventsintegerEvery `delivery_events` row in the window, tracking included -- opens, clicks and unsubscribes as well as sends. NOT a message count: it is roughly the message count multiplied by how much the recipients engaged. Null means the figure is unavailable, never that it is zero.
day_of_weekarray<object>bucketstring`"Monday"` to `"Sunday"`, in English.
eventsintegerEvery `delivery_events` row in the window, tracking included -- opens, clicks and unsubscribes as well as sends. NOT a message count: it is roughly the message count multiplied by how much the recipients engaged. Null means the figure is unavailable, never that it is zero.
Returned inside the standard envelope.
Errors
| Status | When |
|---|---|
401 | The API key is missing, unknown, revoked or expired. All four answer identically, on purpose: distinguishing them would confirm which keys exist. |
403 | The key is valid but may not do this: it lacks the required scope, its IP allowlist does not include you, or this endpoint does not accept API keys. |
404 | No such resource in this organization. |
422 | The request was understood but the values were not acceptable. |
429 | Too many requests, or the organization has spent its sending allowance. `Retry-After` says how long to wait. |
Every status, with what causes it and what to do, is on the error reference.