> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.rray.app/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.rray.app/_mcp/server.

# Get Stats

GET https://api.rray.app/v1/stats

Delivery counters bucketed over time, plus the totals for the window. The series is
gap-filled: every bucket in the requested window is present, and an empty one is
zeros, so a chart needs no gap-filling of its own.

Counters are raw — derive rates yourself. Ask for `compare=previous` to get the same
totals for the window immediately before this one.

Reference: https://docs.rray.app/api/api-reference/stats/get

## Authentication

- `Authorization` header (bearer token, required) — A workspace key (`rray_live_…`, bound to one workspace) or the account's platform key. Platform keys name the target workspace with `RRay-Workspace-Id`. See [Credentials](https://docs.rray.app/docs/concepts/authentication).

## Request

### Query parameters

- `group_by` (enum, optional) — Bucket size: `day`, `week` (ISO-8601, Monday-start), or `month` (calendar).
  - Allowed values: `day`, `week`, `month`
- `last` (integer, optional, nullable) — Number of buckets to return, including the current (partial) one. Defaults to 1: the current (partial) bucket only. Hard ceilings depend on `group_by`: day 90, week 52, month 24.
- `compare` (enum, optional, nullable) — Opt-in comparison window. `previous`: the `last` buckets immediately preceding the primary window, same alignment rules. Adds `previous_totals` to the response — omitted entirely when `compare` is not set, an object of honest zeros when the prior window had no traffic, or `null` when the prior window starts before the retention horizon (unknown, not zero).
  - Allowed values: `previous`

### Headers

- `RRay-Workspace-Id` (string, optional, nullable) — The workspace to act on. Required with a platform key; rejected with a workspace key, which already carries its workspace — a matching value is rejected too, rather than silently ignored. An id that is not yours returns `404 workspace.not_found`.
- `RRay-Workspace-Ids` (list of string, optional) — Aggregate over several workspaces: 1 to 50 comma-separated workspace ids, each one yours. Platform keys only — rejected with a workspace key, which only ever reports its own workspace. Mutually exclusive with `RRay-Workspace-Id`; sending both is always `400 validation.workspace_header_conflict`. An id that is not yours fails the whole request with `404 workspace.not_found` rather than being skipped.

## Response

### 200

Successful Response

- `group_by` (enum, required) — Bucket size the series was built with.
  - Allowed values: `day`, `week`, `month`
- `series` (list of StatsSeriesBucket, required) — One bucket per period in the window, oldest first and gap-filled — an empty period is present with zeros.
- `totals` (StatsTotals, required) — Sum of every counter over the whole window.
- `computed_at` (datetime, required) — When these numbers were computed — realtime today (the request time); will become the last rollup-refresh time if/when a rollup table replaces the on-the-fly aggregation, without changing this field's meaning.
- `object` ("stats", optional, default: stats) — Always `stats`.
- `previous_totals` (StatsTotals, optional, nullable) — Totals for the window immediately before this one. Present only when you asked for `compare=previous`, and `null` there when the earlier window starts before the retained history (unknown, not zero).

## Errors

### 400 Bad Request Error

Bad Request

- `type` (string, required) — Dereferenceable URL to this error's entry in the public /errors catalog.
- `title` (string, required) — Human-readable summary of the error code (constant per `code`).
- `status` (integer, required) — HTTP status code, determined by `code`.
- `code` (enum, required) — Stable machine-readable error key to match on. Full catalog: `GET /errors`.
  - Allowed values: `api_key.not_found`, `app.internal_error`, `auth.dashboard_session_required`, `auth.forbidden`, `auth.platform_credential_required`, `auth.provider_unavailable`, `auth.unauthorized`, `billing.daily_cap_reached`, `billing.outstanding_invoice`, `billing.overage_cap_reached`, `billing.payment_method_required`, `billing.plan_quota_exhausted`, `billing.provider_error`, `billing.suspended`, `billing.workspace_limit_exceeded`, `domain.already_exists`, `error_type.not_found`, `idempotency.body_mismatch`, `idempotency.conflict`, `message.body_unavailable`, `message.injection_failed`, `message.not_found`, `pat.name_taken`, `pat.not_found`, `rate_limit.exceeded`, `resource.conflict`, `resource.not_found`, `validation.attachment_too_large`, `validation.attachment_url_forbidden`, `validation.batch_empty`, `validation.batch_invalid`, `validation.batch_too_large`, `validation.cursor_filters_mismatch`, `validation.cursor_invalid`, `validation.duplicate_recipient`, `validation.failed`, `validation.invalid_attachment`, `validation.limit_below_usage`, `validation.limit_exceeds_capacity`, `validation.overage_cap_unavailable_on_free`, `validation.request_invalid`, `validation.workspace_header_conflict`, `validation.workspace_header_invalid`, `validation.workspace_header_required`, `workspace.name_taken`, `workspace.not_found`
- `category` (enum, required) — Coarse error class for client handling (one level above `code`): validation / authentication / authorization / not_found / conflict / rate_limit / billing / upstream / server.
  - Allowed values: `validation`, `authentication`, `authorization`, `not_found`, `conflict`, `rate_limit`, `billing`, `upstream`, `server`
- `detail` (string, optional, nullable) — Human-readable explanation of this error (constant per `code`); omitted when `title` says it all.
- `request_id` (string, optional, nullable) — Correlation id of the request, for support.
- `instance` (string, optional, nullable) — Path of the request that produced this error.
- `invalid_params` (list of ValidationIssue, optional, nullable) — Request fields that failed validation; present on field-scoped errors, else omitted.
- `state_errors` (list of StateError, optional, nullable) — Resource-state preconditions that failed; present on state-scoped errors, else omitted.

### 401 Unauthorized Error

Unauthorized

- `type` (string, required) — Dereferenceable URL to this error's entry in the public /errors catalog.
- `title` (string, required) — Human-readable summary of the error code (constant per `code`).
- `status` (integer, required) — HTTP status code, determined by `code`.
- `code` (enum, required) — Stable machine-readable error key to match on. Full catalog: `GET /errors`.
  - Allowed values: `api_key.not_found`, `app.internal_error`, `auth.dashboard_session_required`, `auth.forbidden`, `auth.platform_credential_required`, `auth.provider_unavailable`, `auth.unauthorized`, `billing.daily_cap_reached`, `billing.outstanding_invoice`, `billing.overage_cap_reached`, `billing.payment_method_required`, `billing.plan_quota_exhausted`, `billing.provider_error`, `billing.suspended`, `billing.workspace_limit_exceeded`, `domain.already_exists`, `error_type.not_found`, `idempotency.body_mismatch`, `idempotency.conflict`, `message.body_unavailable`, `message.injection_failed`, `message.not_found`, `pat.name_taken`, `pat.not_found`, `rate_limit.exceeded`, `resource.conflict`, `resource.not_found`, `validation.attachment_too_large`, `validation.attachment_url_forbidden`, `validation.batch_empty`, `validation.batch_invalid`, `validation.batch_too_large`, `validation.cursor_filters_mismatch`, `validation.cursor_invalid`, `validation.duplicate_recipient`, `validation.failed`, `validation.invalid_attachment`, `validation.limit_below_usage`, `validation.limit_exceeds_capacity`, `validation.overage_cap_unavailable_on_free`, `validation.request_invalid`, `validation.workspace_header_conflict`, `validation.workspace_header_invalid`, `validation.workspace_header_required`, `workspace.name_taken`, `workspace.not_found`
- `category` (enum, required) — Coarse error class for client handling (one level above `code`): validation / authentication / authorization / not_found / conflict / rate_limit / billing / upstream / server.
  - Allowed values: `validation`, `authentication`, `authorization`, `not_found`, `conflict`, `rate_limit`, `billing`, `upstream`, `server`
- `detail` (string, optional, nullable) — Human-readable explanation of this error (constant per `code`); omitted when `title` says it all.
- `request_id` (string, optional, nullable) — Correlation id of the request, for support.
- `instance` (string, optional, nullable) — Path of the request that produced this error.
- `invalid_params` (list of ValidationIssue, optional, nullable) — Request fields that failed validation; present on field-scoped errors, else omitted.
- `state_errors` (list of StateError, optional, nullable) — Resource-state preconditions that failed; present on state-scoped errors, else omitted.

### 404 Not Found Error

Not Found

- `type` (string, required) — Dereferenceable URL to this error's entry in the public /errors catalog.
- `title` (string, required) — Human-readable summary of the error code (constant per `code`).
- `status` (integer, required) — HTTP status code, determined by `code`.
- `code` (enum, required) — Stable machine-readable error key to match on. Full catalog: `GET /errors`.
  - Allowed values: `api_key.not_found`, `app.internal_error`, `auth.dashboard_session_required`, `auth.forbidden`, `auth.platform_credential_required`, `auth.provider_unavailable`, `auth.unauthorized`, `billing.daily_cap_reached`, `billing.outstanding_invoice`, `billing.overage_cap_reached`, `billing.payment_method_required`, `billing.plan_quota_exhausted`, `billing.provider_error`, `billing.suspended`, `billing.workspace_limit_exceeded`, `domain.already_exists`, `error_type.not_found`, `idempotency.body_mismatch`, `idempotency.conflict`, `message.body_unavailable`, `message.injection_failed`, `message.not_found`, `pat.name_taken`, `pat.not_found`, `rate_limit.exceeded`, `resource.conflict`, `resource.not_found`, `validation.attachment_too_large`, `validation.attachment_url_forbidden`, `validation.batch_empty`, `validation.batch_invalid`, `validation.batch_too_large`, `validation.cursor_filters_mismatch`, `validation.cursor_invalid`, `validation.duplicate_recipient`, `validation.failed`, `validation.invalid_attachment`, `validation.limit_below_usage`, `validation.limit_exceeds_capacity`, `validation.overage_cap_unavailable_on_free`, `validation.request_invalid`, `validation.workspace_header_conflict`, `validation.workspace_header_invalid`, `validation.workspace_header_required`, `workspace.name_taken`, `workspace.not_found`
- `category` (enum, required) — Coarse error class for client handling (one level above `code`): validation / authentication / authorization / not_found / conflict / rate_limit / billing / upstream / server.
  - Allowed values: `validation`, `authentication`, `authorization`, `not_found`, `conflict`, `rate_limit`, `billing`, `upstream`, `server`
- `detail` (string, optional, nullable) — Human-readable explanation of this error (constant per `code`); omitted when `title` says it all.
- `request_id` (string, optional, nullable) — Correlation id of the request, for support.
- `instance` (string, optional, nullable) — Path of the request that produced this error.
- `invalid_params` (list of ValidationIssue, optional, nullable) — Request fields that failed validation; present on field-scoped errors, else omitted.
- `state_errors` (list of StateError, optional, nullable) — Resource-state preconditions that failed; present on state-scoped errors, else omitted.

### 422 Unprocessable Entity Error

Unprocessable Entity

- `type` (string, required) — Dereferenceable URL to this error's entry in the public /errors catalog.
- `title` (string, required) — Human-readable summary of the error code (constant per `code`).
- `status` (integer, required) — HTTP status code, determined by `code`.
- `code` (enum, required) — Always `validation.request_invalid` for a schema-validation failure.
  - Allowed values: `validation.request_invalid`
- `category` (enum, required) — Always `validation` for a schema-validation failure.
  - Allowed values: `validation`
- `invalid_params` (list of ValidationIssue, required) — The request fields that failed schema validation.
- `detail` (string, optional, nullable) — Human-readable explanation of this error (constant per `code`); omitted when `title` says it all.
- `request_id` (string, optional, nullable) — Correlation id of the request, for support.
- `instance` (string, optional, nullable) — Path of the request that produced this error.
- `state_errors` (list of StateError, optional, nullable) — Resource-state preconditions that failed; present on state-scoped errors, else omitted.

### 429 Too Many Requests Error

Too Many Requests

- `type` (string, required) — Dereferenceable URL to this error's entry in the public /errors catalog.
- `title` (string, required) — Human-readable summary of the error code (constant per `code`).
- `status` (integer, required) — HTTP status code, determined by `code`.
- `code` (enum, required) — Stable machine-readable error key to match on. Full catalog: `GET /errors`.
  - Allowed values: `api_key.not_found`, `app.internal_error`, `auth.dashboard_session_required`, `auth.forbidden`, `auth.platform_credential_required`, `auth.provider_unavailable`, `auth.unauthorized`, `billing.daily_cap_reached`, `billing.outstanding_invoice`, `billing.overage_cap_reached`, `billing.payment_method_required`, `billing.plan_quota_exhausted`, `billing.provider_error`, `billing.suspended`, `billing.workspace_limit_exceeded`, `domain.already_exists`, `error_type.not_found`, `idempotency.body_mismatch`, `idempotency.conflict`, `message.body_unavailable`, `message.injection_failed`, `message.not_found`, `pat.name_taken`, `pat.not_found`, `rate_limit.exceeded`, `resource.conflict`, `resource.not_found`, `validation.attachment_too_large`, `validation.attachment_url_forbidden`, `validation.batch_empty`, `validation.batch_invalid`, `validation.batch_too_large`, `validation.cursor_filters_mismatch`, `validation.cursor_invalid`, `validation.duplicate_recipient`, `validation.failed`, `validation.invalid_attachment`, `validation.limit_below_usage`, `validation.limit_exceeds_capacity`, `validation.overage_cap_unavailable_on_free`, `validation.request_invalid`, `validation.workspace_header_conflict`, `validation.workspace_header_invalid`, `validation.workspace_header_required`, `workspace.name_taken`, `workspace.not_found`
- `category` (enum, required) — Coarse error class for client handling (one level above `code`): validation / authentication / authorization / not_found / conflict / rate_limit / billing / upstream / server.
  - Allowed values: `validation`, `authentication`, `authorization`, `not_found`, `conflict`, `rate_limit`, `billing`, `upstream`, `server`
- `detail` (string, optional, nullable) — Human-readable explanation of this error (constant per `code`); omitted when `title` says it all.
- `request_id` (string, optional, nullable) — Correlation id of the request, for support.
- `instance` (string, optional, nullable) — Path of the request that produced this error.
- `invalid_params` (list of ValidationIssue, optional, nullable) — Request fields that failed validation; present on field-scoped errors, else omitted.
- `state_errors` (list of StateError, optional, nullable) — Resource-state preconditions that failed; present on state-scoped errors, else omitted.

### 500 Internal Server Error

Internal Server Error

- `type` (string, required) — Dereferenceable URL to this error's entry in the public /errors catalog.
- `title` (string, required) — Human-readable summary of the error code (constant per `code`).
- `status` (integer, required) — HTTP status code, determined by `code`.
- `code` (enum, required) — Stable machine-readable error key to match on. Full catalog: `GET /errors`.
  - Allowed values: `api_key.not_found`, `app.internal_error`, `auth.dashboard_session_required`, `auth.forbidden`, `auth.platform_credential_required`, `auth.provider_unavailable`, `auth.unauthorized`, `billing.daily_cap_reached`, `billing.outstanding_invoice`, `billing.overage_cap_reached`, `billing.payment_method_required`, `billing.plan_quota_exhausted`, `billing.provider_error`, `billing.suspended`, `billing.workspace_limit_exceeded`, `domain.already_exists`, `error_type.not_found`, `idempotency.body_mismatch`, `idempotency.conflict`, `message.body_unavailable`, `message.injection_failed`, `message.not_found`, `pat.name_taken`, `pat.not_found`, `rate_limit.exceeded`, `resource.conflict`, `resource.not_found`, `validation.attachment_too_large`, `validation.attachment_url_forbidden`, `validation.batch_empty`, `validation.batch_invalid`, `validation.batch_too_large`, `validation.cursor_filters_mismatch`, `validation.cursor_invalid`, `validation.duplicate_recipient`, `validation.failed`, `validation.invalid_attachment`, `validation.limit_below_usage`, `validation.limit_exceeds_capacity`, `validation.overage_cap_unavailable_on_free`, `validation.request_invalid`, `validation.workspace_header_conflict`, `validation.workspace_header_invalid`, `validation.workspace_header_required`, `workspace.name_taken`, `workspace.not_found`
- `category` (enum, required) — Coarse error class for client handling (one level above `code`): validation / authentication / authorization / not_found / conflict / rate_limit / billing / upstream / server.
  - Allowed values: `validation`, `authentication`, `authorization`, `not_found`, `conflict`, `rate_limit`, `billing`, `upstream`, `server`
- `detail` (string, optional, nullable) — Human-readable explanation of this error (constant per `code`); omitted when `title` says it all.
- `request_id` (string, optional, nullable) — Correlation id of the request, for support.
- `instance` (string, optional, nullable) — Path of the request that produced this error.
- `invalid_params` (list of ValidationIssue, optional, nullable) — Request fields that failed validation; present on field-scoped errors, else omitted.
- `state_errors` (list of StateError, optional, nullable) — Resource-state preconditions that failed; present on state-scoped errors, else omitted.

## Types

### StatsSeriesBucket

One bucket of the GET /v1/stats series. ``period_start`` is a full RFC 3339 UTC datetime (midnight for every current `group_by`: UTC day, ISO-Monday for week, the 1st for month) — a datetime rather than a date so a future sub-day `group_by` can land without a breaking type change, and so week/month buckets are unambiguous about which end of the period they name. Fields are gap-filled: every bucket in the requested window is present, empty ones are zero — no client-side gap-filling logic needed to render a chart.

- `period_start` (datetime, required) — Start of this bucket (RFC 3339, UTC).
- `accepted` (integer, required) — Deliveries currently `accepted` (not yet queued).
- `queued` (integer, required) — Deliveries queued for delivery.
- `delivered` (integer, required) — Deliveries that reached the recipient's mailbox provider.
- `bounced` (integer, required) — Deliveries permanently rejected.
- `deferred` (integer, required) — Deliveries temporarily failed, still retrying.
- `failed` (integer, required) — Deliveries that exhausted retries without delivering.
- `messages_accepted` (integer, required) — Messages (sends) accepted in this bucket — one per request item regardless of recipient count, unlike the delivery counters above.

### StatsTotals

Sum of every ``StatsSeriesBucket`` field over the requested window.

- `accepted` (integer, required) — Deliveries currently `accepted` (not yet queued).
- `queued` (integer, required) — Deliveries queued for delivery.
- `delivered` (integer, required) — Deliveries that reached the recipient's mailbox provider.
- `bounced` (integer, required) — Deliveries permanently rejected.
- `deferred` (integer, required) — Deliveries temporarily failed, still retrying.
- `failed` (integer, required) — Deliveries that exhausted retries without delivering.
- `messages_accepted` (integer, required) — Messages (sends) accepted over the window — one per request item regardless of recipient count, unlike the delivery counters above.

### ValidationIssue

One failed field — an entry of the ``invalid_params`` array (a request field the caller can fix). Shared by schema-validation failures (pydantic) and field-scoped business rules (e.g. a duplicate recipient), so the two cannot drift in shape.

- `field` (string, required) — Path of the request field that failed, e.g. `to`, `attachments[0]`.
- `message` (string, required) — Human-readable explanation of this field error (safe to surface in a UI).
- `kind` (string, required) — Machine-readable CAUSE token (finer than the error `code`): a pydantic type (`missing`, `string_too_long`, `value_error`) for a schema failure, or a business cause — `duplicate`, `invalid_base64`, `unsupported_scheme`, `missing_host`, `unresolvable_host`, `private_address`, `size_exceeded`, `plan_restricted`. Branch on this, not `message`.

### StateError

One failed resource-state precondition — an entry of the ``state_errors`` array. Unlike ``invalid_params`` it points at a resource/account state (not a request field), e.g. an unverified sender domain or insufficient credits.

- `resource` (string, required) — The resource whose state blocked the request, e.g. `sender_domain`.
- `message` (string, required) — Human-readable explanation of the state problem (safe to surface in a UI).
- `kind` (string, required) — Machine-readable CAUSE token (finer than the error `code`), e.g. `unverified`.
- `resource_id` (string, optional, nullable) — Identifier of that resource, e.g. `example.com`.

## Examples

**Request**

```json
{}
```

**Response**

```json
{
  "group_by": "day",
  "series": [
    {
      "period_start": "2024-01-13T00:00:00Z",
      "accepted": 12,
      "queued": 8,
      "delivered": 1847,
      "bounced": 23,
      "deferred": 41,
      "failed": 7,
      "messages_accepted": 1938
    },
    {
      "period_start": "2024-01-14T00:00:00Z",
      "accepted": 5,
      "queued": 3,
      "delivered": 2104,
      "bounced": 18,
      "deferred": 29,
      "failed": 4,
      "messages_accepted": 2163
    },
    {
      "period_start": "2024-01-15T00:00:00Z",
      "accepted": 31,
      "queued": 47,
      "delivered": 983,
      "bounced": 9,
      "deferred": 15,
      "failed": 2,
      "messages_accepted": 1087
    }
  ],
  "totals": {
    "accepted": 48,
    "queued": 58,
    "delivered": 4934,
    "bounced": 50,
    "deferred": 85,
    "failed": 13,
    "messages_accepted": 5188
  },
  "computed_at": "2024-01-15T14:22:37Z",
  "object": "stats",
  "previous_totals": {
    "accepted": 22,
    "queued": 34,
    "delivered": 4571,
    "bounced": 61,
    "deferred": 97,
    "failed": 19,
    "messages_accepted": 4804
  }
}
```

**SDK Code**

```python
import requests

url = "https://api.rray.app/v1/stats"

payload = {}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.get(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.rray.app/v1/stats';
const options = {
  method: 'GET',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.rray.app/v1/stats"

	payload := strings.NewReader("{}")

	req, _ := http.NewRequest("GET", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.rray.app/v1/stats")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.rray.app/v1/stats")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.rray.app/v1/stats', [
  'body' => '{}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.rray.app/v1/stats");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.rray.app/v1/stats")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```