GET /v1/messages
Docs / Sending
GET

/v1/messages

Lists messages in the archive for this Account — newest first. Returns summary rows (status, evidence, subject, tags), not bodies. Bodies live on .

Authentication

Authorization: Bearer spw_…

Requires messages:read. A messages:send-only key gets 403.

Keys rule — prefix, shown once, abilities never widen, and no key carries both messages:send and messages:read-content.

Query parameters

limitintegeroptionalPage size, 1–100. Default 25.
cursorstring (uuid)optionalThe next_cursor from the previous page; the listing resumes after that message. A cursor whose message was erased or aged out answers 422, type cursor_expired. See Pagination.
application_idintegeroptionalFilter to one Application id.
statusstringoptionalFilter by status: queued, sending, sent, failed, suppressed.

Fields

Each row is the summary GET /v1/messages/{id} also returns. Recipients, events and attachments are on that endpoint only.

evidenceOne of none, smtp_accepted, provider_delivered, provider_bounced, provider_complained, provider_deferred, suppressed. While status is sent, a delivered event on a transport that reports delivery is provider_delivered, including when an earlier deferral or another recipient's bounce or complaint is also recorded. Otherwise a complaint event is chosen first, then a bounce, then a defer while the message is still queued, sending, or deferred, then a delivered event on a transport that reports delivery. Otherwise sent is smtp_accepted, suppressed is suppressed, and failed or anything else is none. The word Delivered is the status label only when status is sent and evidence is provider_delivered. Sent with any other evidence, including smtp_accepted and provider_deferred, reads Handed to your server.
status_labelQueued, Sending, Handed to your server, Delivered, Failed, Bounced, Complained, or Suppressed. Bounced is a failed message whose evidence is provider_bounced. Complained is a failed message whose evidence is provider_complained. Any other failure reads Failed.
subject_redactedTrue when the template is marked sensitive. The message subject is then null. The real subject is stored with the body and returned by the content endpoint only for a read-content key minted by an owner. A read-content key minted by anyone else is 403, type sealed_body_denied.
content_expires_atNull until the message reaches a final state. Then the completion time when the retention in force is 0 days, otherwise that time plus the retention days. The retention is the template's own setting if it has one, otherwise the account's, capped by the plan. The stored body carries the same instant. Opening it after that time is 410, message Content expired.

Worked example

curl
curl https://spoolway.com/api/v1/messages?limit=25 \
  -H "Authorization: Bearer spw_…"
PHP
$ch = curl_init("https://spoolway.com/api/v1/messages?limit=25");
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SPOOLWAY_KEY")],
]);
$page = json_decode(curl_exec($ch), true);
Node
const res = await fetch("https://spoolway.com/api/v1/messages?limit=25", {
  headers: { Authorization: `Bearer ${process.env.SPOOLWAY_KEY}` },
});
const page = await res.json();
Python
import os, requests

page = requests.get(
    "https://spoolway.com/api/v1/messages",
    headers={"Authorization": f"Bearer {os.environ['SPOOLWAY_KEY']}"},
    params={"limit": 25},
).json()
Ruby
require "net/http"; require "json"

uri = URI("https://spoolway.com/api/v1/messages?limit=25")
req = Net::HTTP::Get.new(uri,
  { "Authorization" => "Bearer #{ENV["SPOOLWAY_KEY"]}" })
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
page = JSON.parse(res.body)
200 OKsummary rows
{
  "data": [
    {
      "id": "2ee35675-6bae-42be-aa5e-6d4618d0c61d",
      "status": "queued",
      "status_label": "Queued",
      "evidence": "none",
      "supports_feedback": false,
      "application_id": 1,
      "template_id": 1,
      "subject": "Reset password",
      "subject_redacted": false,
      "from_address": "[email protected]",
      "queued_at": "2026-08-06T06:55:48.000000Z",
      "completed_at": null,
      "content_expires_at": null,
      "tags": { "env": "docs" }
    }
  ],
  "next_cursor": null
}
HONESTY NOTE — STATUS VS EVIDENCEstatus is the machine state. status_label and evidence are what the panel shows — on plain SMTP, Handed to your server with smtp_accepted, never a green Delivered without a feedback feed.

Responses & failures

401 unauthenticated, 403 when the key lacks messages:read. A key scoped away from the Application named in application_id is also 403, message "This key cannot list messages for that application." There is no type. A cursor whose message was erased or has aged out is 422 with type: cursor_expired. A cursor that is not a uuid is a validation 422 with errors, not cursor_expired. The status filter is an exact match on the stored status. A value that matches no message returns an empty page — data is an empty list and next_cursor is null — rather than a 422. Full matrix under .