Skip to content
OS Domains
API reference · v2

Eight endpoint groups. REST, JSON, predictable.

This is the complete v2 REST API reference. Authentication is a bearer token, request and response bodies are JSON, errors follow a standardized envelope, and pagination is cursor-based. Where convenient, we link to the docs page that goes deeper. The OpenAPI 3.1 specification is at /openapi.yaml if you prefer to generate clients yourself.

v1 is still supported through 2027-12-31 but receives no new features. New integrations should use v2.

API metadata
Base URL
https://api.osdomains.com/v2
Auth
Bearer token
Format
JSON · UTF-8
Rate limit
60,000 req/min default
Versioning
URL path (/v2)
OpenAPI spec
/openapi.yaml
Authentication

Bearer tokens. Per-environment. Scoped.

Every API request must include an Authorization: Bearer YOUR_API_KEY header. Generate API keys in the customer portal under Settings → API Keys, scoped to specific operations (send, read, admin) and to specific environments (production, staging). Treat keys like passwords — never commit them, rotate every 180 days, revoke immediately if exposed.

REQUEST HEADERS
Authorization: Bearer osd_live_8f4e2d1a3b6c9f7e2a5d8b1c4f7e0a3b
Content-Type: application/json
Idempotency-Key: 0e7d3a82-4b6c-4f17-92e1-3b5d8c9f1e2a  # optional, recommended for POST
Endpoints

Eight endpoint groups.

POST /v2/send 01

Send

Submit a single message or a batch for outbound delivery.

Parameters

Name Type Required Description
from string yes Verified sending address. The domain must be added in Domains and have valid SPF/DKIM.
to string | string[] yes Single recipient or array up to 1,000. Batches return one ID per recipient.
subject string yes Message subject. Plain text; HTML entities allowed.
html string no HTML body. Either html, text, or both must be provided.
text string no Plain-text body. We strongly recommend including this even alongside html.
headers object no Custom headers. Use X-OSD- prefix to keep them in your namespace.
cc string[] no Cc recipients. Counts against your monthly volume.
bcc string[] no Bcc recipients. Counts against your monthly volume.
attachments Attachment[] no Up to 10 attachments per message, 25MB combined.
template_id string no Use a saved template; merge variables via the data field.
data object no Merge variables for template rendering.
reply_to string no Reply-To address (different from From).
send_at ISO 8601 string no Schedule for future send. Maximum 72 hours ahead.

Example request body

{
  "from": "[email protected]",
  "to": "[email protected]",
  "subject": "Welcome to the product",
  "html": "<p>Hello there.</p>",
  "text": "Hello there.",
  "headers": {
    "X-OSD-Campaign-ID": "welcome-v3"
  }
}

Example response (200 OK)

{
  "id": "msg_01HXAM5K7Z3P8B5R2W7K8Q4N6F",
  "status": "queued",
  "to": "[email protected]",
  "submitted_at": "2026-05-11T14:23:51Z"
}
GET / POST /v2/templates 02

Templates

List, create, update, delete reusable email templates with Mustache/Handlebars syntax.

Parameters

Name Type Required Description
name string yes Unique template slug.
subject string yes Subject with optional merge vars.
html string no HTML body with optional merge vars.
text string no Plain-text body with optional merge vars.

Example request body

{
  "name": "welcome-email",
  "subject": "Welcome, {{firstName}}",
  "html": "<p>Hi {{firstName}}, thanks for joining.</p>",
  "text": "Hi {{firstName}}, thanks for joining."
}

Example response (200 OK)

{
  "id": "tpl_01HXAM6K8Z3P8B5R2W7K8Q4N6F",
  "name": "welcome-email",
  "version": 1,
  "created_at": "2026-05-11T14:23:51Z"
}
GET / POST /v2/domains 03

Domains

List sending domains, verify DNS records, manage domain settings.

Parameters

Name Type Required Description
domain string yes FQDN of the sending domain. Subdomain recommended (e.g., mailers.yourdomain.com).
dkim_selector string no Custom DKIM selector. Defaults to osd2026 with annual rotation.

Example request body

{
  "domain": "mailers.yourdomain.com",
  "dkim_selector": "osd2026"
}

Example response (200 OK)

{
  "id": "dom_01HXAM7K8Z3P8B5R2W7K8Q4N6F",
  "domain": "mailers.yourdomain.com",
  "verified": false,
  "records": {
    "spf":   { "name": "mailers.yourdomain.com",          "value": "v=spf1 include:_spf.osdomains.com ~all",                  "type": "TXT" },
    "dkim":  { "name": "osd2026._domainkey.mailers...",   "value": "v=DKIM1; k=rsa; p=...",                                   "type": "TXT" },
    "dmarc": { "name": "_dmarc.mailers.yourdomain.com",   "value": "v=DMARC1; p=none; rua=mailto:[email protected]",        "type": "TXT" },
    "mx":    { "name": "mailers.yourdomain.com",          "value": "10 inbound.osdomains.com",                                "type": "MX" }
  }
}
GET /v2/ips 04

IPs

List dedicated IPs, warm-up status, rDNS hostnames, pool assignments.

Example response (200 OK)

{
  "items": [
    {
      "id": "ip_01HXAM8K8Z3P8B5R2W7K8Q4N6F",
      "address": "185.225.43.42",
      "ptr": "mailers.yourdomain.com",
      "pop": "VIE",
      "pool": "default",
      "warmup_status": "warm",
      "daily_cap": "unlimited",
      "reputation": { "gmail": "high", "outlook": "high" }
    }
  ],
  "next_cursor": null
}
GET / POST / DELETE /v2/suppressions 05

Suppressions

Manage the suppression list (bounces, complaints, manual unsubscribes).

Parameters

Name Type Required Description
address string yes Email address to suppress.
reason enum yes manual, hard_bounce, complaint, spam_trap, unknown_user, mailbox_full, content_rejected.
note string no Free-text reason. Visible in audit log.

Example request body

{
  "address": "[email protected]",
  "reason": "manual",
  "note": "Customer requested removal via support call 2026-05-10"
}

Example response (200 OK)

{
  "address": "[email protected]",
  "reason": "manual",
  "added_at": "2026-05-11T14:23:51Z",
  "expires_at": null
}
GET / POST /v2/webhooks 06

Webhooks

Configure HTTP endpoints that receive real-time message events.

Parameters

Name Type Required Description
url string yes HTTPS endpoint that receives events.
events string[] yes Array of event types. See Event Types reference below.
signing_secret string no Provide your own, or pass "auto" to generate one. Used to compute X-OSD-Signature header.

Example request body

{
  "url": "https://api.yourapp.com/osd-webhooks",
  "events": ["delivered", "bounced", "complained"],
  "signing_secret": "auto"
}

Example response (200 OK)

{
  "id": "wh_01HXAM9K8Z3P8B5R2W7K8Q4N6F",
  "url": "https://api.yourapp.com/osd-webhooks",
  "events": ["delivered", "bounced", "complained"],
  "signing_secret": "whsec_8f4e2d1a3b6c9f7e2a5d8b1c4f7e0a3b",
  "active": true
}
GET /v2/stats 07

Stats

Aggregated send / deliver / bounce / complaint counts by time window, domain, or IP.

Parameters

Name Type Required Description
from ISO 8601 string yes Start of window (UTC).
to ISO 8601 string yes End of window (UTC). Maximum range 90 days.
group_by enum no provider, domain, ip, template, campaign, hour, day.

Example response (200 OK)

{
  "window": { "from": "2026-05-01T00:00:00Z", "to": "2026-05-11T00:00:00Z" },
  "totals": {
    "sent": 4823910,
    "delivered": 4789203,
    "bounced": 28104,
    "deferred": 5102,
    "complained": 1491,
    "delivery_rate": 0.9928,
    "complaint_rate": 0.00031
  },
  "by_provider": {
    "gmail":    { "delivered": 2310498, "delivery_rate": 0.9942 },
    "outlook":  { "delivered": 1290340, "delivery_rate": 0.9912 },
    "yahoo":    { "delivered": 521093,  "delivery_rate": 0.9897 }
  }
}
GET /v2/status 08

Status

Programmatic access to the public status data shown at /status.

Example response (200 OK)

{
  "overall": "operational",
  "components": {
    "mta-cluster": "operational",
    "smtp-relay":  "operational",
    "email-api":   "operational",
    "dns":         "operational"
  },
  "incidents_open": 0,
  "maintenance_next": null,
  "checked_at": "2026-05-11T14:23:51Z"
}
Webhook event types

Eight event types we deliver to your webhooks.

Event Description
queued Message accepted by the API, awaiting MTA dispatch. Fires within 1 second of API submission.
delivered Recipient MTA returned 2xx. Best-effort confirmation of inbox arrival (provider-specific).
deferred Temporary failure (4xx). We retry automatically; final outcome arrives later.
bounced Permanent failure (5xx). Recipient is auto-added to suppression list.
complained Recipient marked as spam via FBL. Recipient is auto-added to suppression list.
opened Open-tracking pixel was loaded (if open tracking is enabled).
clicked A tracked link was clicked (if click tracking is enabled).
unsubscribed Recipient clicked List-Unsubscribe or One-Click List-Unsubscribe.
Error codes

HTTP error envelope and ten error codes.

All errors return a JSON envelope of the form below. The retry_after field is present on 429 and 503 responses; honor it via exponential backoff.

ERROR ENVELOPE
{
  "error": {
    "code": "unprocessable_entity",
    "message": "Domain not verified. Publish DKIM records and call POST /v2/domains/:id/verify.",
    "doc_url": "https://osdomains.com/docs#domains",
    "request_id": "req_01HXAM9K8Z3P8B5R2W7K8Q4N6F"
  }
}
HTTP code When this fires
400 bad_request Malformed JSON, missing required field, invalid email address format.
401 unauthorized Missing or invalid API key. Check the Authorization header.
403 forbidden API key valid but lacks scope for this operation. Generate a key with proper scope.
404 not_found Resource (template, webhook, domain) does not exist or belongs to another account.
409 conflict Idempotency key matched a different request body. Use a new key.
422 unprocessable_entity Domain not verified, recipient on suppression list, template merge variable missing.
429 rate_limited You exceeded the per-key rate limit. Honor the Retry-After header.
500 internal_error Our problem, not yours. Retry with exponential backoff; check status.osdomains.com.
502 bad_gateway Upstream MTA temporarily unreachable from API gateway. Retry within 60 seconds.
503 service_unavailable Degraded service. Retry with exponential backoff; check status.osdomains.com.
Pagination

Cursor-based

Endpoints that return collections paginate via cursor. Pass ?limit=100 (default 50, max 1000) and follow the next_cursor field of the response in your next request as ?cursor=.... When next_cursor is null, you have reached the end.

GET /v2/suppressions?limit=100
→ next_cursor: "c_8f4e2d..."

GET /v2/suppressions?limit=100&cursor=c_8f4e2d...
→ next_cursor: null   # final page
Rate limits

60,000 requests per minute

Default rate limit is 60,000 requests per minute per API key. Headers in every response tell you where you stand. Hit 429? Honor Retry-After.

X-RateLimit-Limit: 60000
X-RateLimit-Remaining: 58423
X-RateLimit-Reset: 2026-05-11T14:24:00Z

# On 429:
Retry-After: 12
Build something

The OpenAPI spec is at /openapi.yaml. SDKs are generated from it.

If you want to generate your own client in a language we do not officially support, point your OpenAPI generator at /openapi.yaml. The spec is regenerated on every API release and never drifts from the implementation. For sample applications in 6 languages, see github.com/osdomains.

Phone +43 1 205 11 80 Mon–Fri · 9–18 CET
Email [email protected] Avg response 4h business
Office Fleischmarkt 1, 1010 Wien By appointment