This is the reference most engineers want and rarely find in one place: every endpoint, every parameter, the auth model, the webhook lifecycle, and how to move off a competitor like GlockApps without rewriting your whole integration. Bookmark it and keep moving.
Backend, DevOps and SRE engineers integrating inbox placement tests into a product, a deployment pipeline, or an internal monitoring tool. You know what Bearer means, you prefer JSON over screenshots, and you want a contract you can lint.
What the API does
Inbox Check API runs a placement test: you submit a message, we fan it out to 20+ seed mailboxes across Gmail, Outlook, Yahoo, Mail.ru, Yandex, GMX, ProtonMail and others, record where each copy landed (Inbox, Promotions, Spam, Missing), and return an aggregate verdict together with SPF, DKIM, DMARC, Rspamd and SpamAssassin data.
The API is REST over HTTPS, JSON in and JSON out. The free tier covers unlimited UI tests; the paid tier unlocks programmatic access, longer retention, webhooks, and higher rate limits.
Authentication
Every request carries a Bearer token. Keys are generated in Settings → API keys, live keys are prefixed ic_live_, sandbox keys are prefixed ic_test_. There is no OAuth layer and no session cookie — a token is the whole identity.
Authorization: Bearer ic_live_4fN2q8s...
Content-Type: application/json
Accept: application/jsonKeys can be scoped read, write, or webhook-sign. Rotate by generating a new key, pushing it to your secret store, and revoking the old one. Keys never expire automatically — revoke explicitly.
Base URL and versioning
The production base URL is https://check.live-direct-marketing.online. The API is currently unversioned in the path; breaking changes will introduce /v2/ and run in parallel for at least 12 months. Non-breaking additions (new fields, new providers) ship without a version bump.
Endpoints
POST /api/tests — create a test
The only required field is html. Everything else has a sensible default.
POST /api/tests HTTP/1.1
Host: check.live-direct-marketing.online
Authorization: Bearer ic_live_xxx
Content-Type: application/json
{
"senderDomain": "news.yourbrand.com",
"fromAddress": "hello@news.yourbrand.com",
"fromName": "Brand Weekly",
"subject": "Your Tuesday digest is ready",
"html": "<html><body><h1>Hello</h1></body></html>",
"text": "Hello",
"headers": {
"List-Unsubscribe": "<https://example.com/u/abc>",
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
},
"seedSet": "default",
"webhookUrl": "https://api.yourapp.com/hooks/inbox-check",
"tags": ["nightly-monitor", "prod"]
}Response, 201 Created:
{
"id": "t_01HG2NQ3B5F8X9WVQ6Z1A2C4D7",
"status": "queued",
"createdAt": "2026-06-18T10:14:03.221Z",
"pollUrl": "/api/tests/t_01HG2NQ3B5F8X9WVQ6Z1A2C4D7",
"streamUrl": "/api/tests/t_01HG2NQ3B5F8X9WVQ6Z1A2C4D7/stream",
"expectedCompletionAt": "2026-06-18T10:19:03.221Z"
}GET /api/tests/:id — fetch one test
Returns the current state. Safe to poll every 10–30 seconds until status is complete or failed.
{
"id": "t_01HG2NQ3B5F8X9WVQ6Z1A2C4D7",
"status": "complete",
"createdAt": "2026-06-18T10:14:03.221Z",
"completedAt": "2026-06-18T10:17:48.002Z",
"summary": {
"inboxCount": 17,
"promoCount": 2,
"spamCount": 2,
"missingCount": 1,
"total": 22,
"inboxRate": 0.7727
},
"auth": {
"spf": { "result": "pass", "domain": "news.yourbrand.com" },
"dkim": { "result": "pass", "selector": "s1" },
"dmarc": { "result": "pass", "policy": "quarantine", "aligned": true }
},
"content": {
"spamAssassin": { "score": 1.4, "rules": ["HTML_MESSAGE","MIME_HTML_ONLY"] },
"rspamd": { "score": -0.8, "symbols": ["R_DKIM_ALLOW","ARC_NA"] }
},
"providers": [
{ "provider": "gmail", "region": "us", "placement": "inbox" },
{ "provider": "outlook", "region": "us", "placement": "spam" },
{ "provider": "yahoo", "region": "us", "placement": "inbox" },
{ "provider": "mail.ru", "region": "ru", "placement": "inbox" },
{ "provider": "yandex", "region": "ru", "placement": "promo" }
]
}GET /api/tests/:id/stream — Server-Sent Events
Subscribe once, receive one event per seed-mailbox landing, close after the final complete event. Events are named; the payload is JSON.
event: landing
data: {"provider":"gmail","region":"us","placement":"inbox","at":"2026-06-18T10:14:44Z"}
event: landing
data: {"provider":"outlook","region":"us","placement":"spam","at":"2026-06-18T10:15:02Z"}
event: complete
data: {"id":"t_01HG2NQ3...","summary":{"inboxCount":17,"total":22,"inboxRate":0.7727}}GET /api/tests — list
Paginated, newest first. Supports ?limit= (max 100), ?cursor=, ?status=, ?tag=, ?since= (ISO timestamp).
GET /api/tests?limit=20&status=complete&tag=prod HTTP/1.1
{
"data": [ /* test objects */ ],
"nextCursor": "c_2026-06-18T09:00:00Z",
"hasMore": true
}GET /api/me — who am I
Returns the key's plan, rate limits and usage counters. Useful for health checks and quota dashboards.
{
"key": { "id": "k_01HF...", "scopes": ["read","write"], "prefix": "ic_live_4fN2q8" },
"plan": "scale",
"limits": { "testsPerMinute": 10, "testsPerMonth": 5000 },
"usage": { "testsThisMonth": 412, "resetsAt": "2026-07-01T00:00:00Z" }
}Webhook lifecycle and signature verification
If webhookUrl is set on a test (or configured globally in Settings), we POST a signed payload on test.completed and test.failed. Every request carries two headers:
X-IC-Signature: sha256=<hex>— HMAC of the raw body with the webhook secret.X-IC-Event: test.completed— event name, use for routing.
Retries: 5 attempts over 24 hours with exponential backoff (30s, 2m, 10m, 1h, 6h). Any 2xx response acknowledges delivery; anything else is retried. Receivers must be idempotent — the same X-IC-Delivery header may be replayed.
Rate limits and 429 handling
Every response includes rate-limit headers so you don't have to guess:
X-RateLimit-Limit— requests per minute allowed on this key.X-RateLimit-Remaining— requests left in the current window.X-RateLimit-Reset— Unix timestamp when the window resets.Retry-After— seconds to wait, only on 429.
On 429, honour Retry-After, then back off exponentially with a cap at 2 minutes. Do not hot-loop.
SDKs
Two official SDKs ship today; a Go SDK is on the roadmap.
Python
pip install inbox-check
from inbox_check import Client
ic = Client(api_key="ic_live_xxx")
test = ic.tests.create(
sender_domain="news.yourbrand.com",
subject="Weekly digest",
html="<p>Hello</p>",
)
result = ic.tests.wait(test.id, timeout=300)
print(result.summary.inbox_rate)Node.js
npm install @inbox-check/sdk
import { InboxCheck } from '@inbox-check/sdk';
const ic = new InboxCheck({ apiKey: process.env.IC_KEY });
const test = await ic.tests.create({
senderDomain: 'news.yourbrand.com',
subject: 'Weekly digest',
html: '<p>Hello</p>',
});
const result = await ic.tests.wait(test.id);
console.log(result.summary.inboxRate);Polling vs streaming vs webhooks — which to pick
- Polling (GET /api/tests/:id). Use in CI jobs under 5 minutes. Dead simple, survives flaky networks. Poll every 15s.
- Streaming (SSE). Use in a long-lived process that wants live per-mailbox updates. Connection survives 30 minutes; one test takes 2–10 minutes.
- Webhooks. Use for anything serverless, event-driven, or user-facing. No connection to keep open, no cron to run.
CI pipelines poll. Dashboards stream. Production systems webhook. Do not mix streaming and webhooks for the same test — you'll end up double-processing the result.
Migration guide from GlockApps API
If you're moving off GlockApps, the mapping is almost 1:1. The concepts (test, seed list, placement, summary) are the same; the names differ.
- GlockApps
POST /tests→ Inbox CheckPOST /api/tests. Body fields:subject,html,fromall map directly.seedListbecomesseedSet. - GlockApps
GET /tests/:id→ same path, same semantics. Summary fields renamed:inbox→inboxCount,deliverability→inboxRate(0..1 instead of 0..100). - Auth: Bearer token instead of
X-Api-Key. Rename the header, done. - Webhooks: identical HMAC-SHA256 scheme, header name changes from
X-GA-SignaturetoX-IC-Signature. - Cost: no per-test overage surprises. Quotas are monthly; once exhausted you get 402 until reset or a plan upgrade.
A typical migration takes a senior engineer under a day. The GlockApps → Inbox Check shim used internally is 80 lines of TypeScript.
Error shape
Every 4xx / 5xx response carries the same JSON shape:
{
"error": {
"code": "rate_limited",
"message": "10 requests per minute exceeded",
"docsUrl": "https://check.live-direct-marketing.online/docs/errors#rate_limited",
"requestId": "req_01HG2NR4..."
}
}Include requestId in support tickets — it pulls up the full trace on our side.