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.
- 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
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.
Authorization: Bearer osd_live_8f4e2d1a3b6c9f7e2a5d8b1c4f7e0a3b
Content-Type: application/json
Idempotency-Key: 0e7d3a82-4b6c-4f17-92e1-3b5d8c9f1e2a # optional, recommended for POST Eight endpoint groups.
/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"
} /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"
} /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" }
}
} /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
} /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
} /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
} /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 }
}
} /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"
} 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. |
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": {
"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. |
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 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 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.