Applications
Docs / Control plane

Applications

An Application is one of your products. List, create, read. Every send names one, so "which product sent this?" is a filter. Create respects the plan's application cap.

GET /v1/applications · POST /v1/applications · GET /v1/applications/{id}

Authentication

Authorization: Bearer spw_…

List and show need applications:read. Create needs applications:write.

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

The object

idInteger id.
nameDisplay name.
slugalpha_dash, unique on the account. The value a send passes as application.
environmentprod, staging, or dev.
daily_send_limitPer-application daily cap, or null to use the account cap.
default_transport_idTransport used when a send names none, or null.
archived_atWhen the application was archived, or null. A send through an archived application is refused.
created_atWhen the row was created, ISO 8601.
The object
{
  "data": {
    "id": 2,
    "name": "Billing",
    "slug": "billing-prod",
    "environment": "prod",
    "daily_send_limit": null,
    "default_transport_id": null,
    "archived_at": null,
    "created_at": "2026-08-06T08:11:01+00:00"
  }
}

List

GET /v1/applications returns these objects in data, plus next_cursor.

curl
curl "https://spoolway.com/api/v1/applications?limit=25" \
  -H "Authorization: Bearer spw_…"
PHP
$ch = curl_init("https://spoolway.com/api/v1/applications?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/applications?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/applications",
    params={"limit": 25},
    headers={"Authorization": f"Bearer {os.environ['SPOOLWAY_KEY']}"},
).json()
Ruby
require "net/http"; require "json"

uri = URI("https://spoolway.com/api/v1/applications?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)

Create parameters

namestring ≤ 255requiredDisplay name.
slugstring ≤ 64requiredalpha_dash. Unique per account.
environmentenumrequiredprod | staging | dev.
daily_send_limitinteger ≥ 1optionalPer-app daily cap; null inherits account.

Create

201 returns the object above.

curl
curl -X POST https://spoolway.com/api/v1/applications \
  -H "Authorization: Bearer spw_…" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Billing", "slug": "billing-prod", "environment": "prod" }'
PHP
$ch = curl_init("https://spoolway.com/api/v1/applications");
curl_setopt_array($ch, [
  CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("SPOOLWAY_KEY"),
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "name" => "Billing", "slug" => "billing-prod", "environment" => "prod",
  ]),
]);
$app = json_decode(curl_exec($ch), true);
Node
const res = await fetch("https://spoolway.com/api/v1/applications", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SPOOLWAY_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Billing", slug: "billing-prod", environment: "prod",
  }),
});
const app = await res.json();
Python
import os, requests

app = requests.post(
    "https://spoolway.com/api/v1/applications",
    headers={"Authorization": f"Bearer {os.environ['SPOOLWAY_KEY']}"},
    json={"name": "Billing", "slug": "billing-prod", "environment": "prod"},
).json()
Ruby
require "net/http"; require "json"

uri = URI("https://spoolway.com/api/v1/applications")
req = Net::HTTP::Post.new(uri, {
  "Authorization" => "Bearer #{ENV["SPOOLWAY_KEY"]}",
  "Content-Type" => "application/json",
})
req.body = { name: "Billing", slug: "billing-prod", environment: "prod" }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
app = JSON.parse(res.body)

Show

One application by id. The body is the same object. Cross-account is 404.

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

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

uri = URI("https://spoolway.com/api/v1/applications/#{id}")
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) }
app = JSON.parse(res.body)

Responses & failures

422slug already on this account
response body
{
  "message": "An application with this slug already exists on this account."
}
What to do: pick a free slug or use the existing application. Plan cap exhaustion is also 422, with type: limit_exceeded and limit.dimension: applications.

401 / 403 as on every other surface. Cross-account show is 404.