API7 min read

API response format — JSON schema and pagination

The response envelope, the JSON schema and the pagination model — reference documentation you can paste straight into your codegen.

Every REST API has three shape decisions that determine whether the client code will be pleasant or miserable: the envelope, the pagination model, and the error format. This page is the reference for all three, with a JSON Schema fragment you can paste into your OpenAPI generator.

Spec highlights

Envelope is { data, error, meta } on every response. Pagination is cursor-based with Link headers (RFC 5988). Timestamps are ISO 8601 with explicit UTC. Errors always carry a requestId you can cite in support tickets.

Response envelope

Every JSON response — success or error — has the same top-level shape:

{
  "data":  <object | array | null>,
  "error": <object | null>,
  "meta":  <object>
}

On success, data is populated and error is null. On failure, the reverse. meta is always present and always contains at least a requestId.

Success example

{
  "data": {
    "id": "t_01H9XABC4D5E6F7G8H",
    "status": "complete",
    "summary": { "inboxCount": 18, "spamCount": 3, "missingCount": 1, "total": 22 }
  },
  "error": null,
  "meta": {
    "requestId":        "req_01H9X7KEYABCDEF",
    "creditsRemaining": 248,
    "version":          "2026-07-01"
  }
}

Error example

{
  "data": null,
  "error": {
    "code":    "validation_error",
    "message": "Field 'from' must be a valid email address",
    "field":   "from",
    "docs":    "https://check.live-direct-marketing.online/docs/errors#validation_error"
  },
  "meta": {
    "requestId": "req_01H9X7KEYBADBEEF",
    "version":   "2026-07-01"
  }
}

Status code map

Inbox Check API: status code reference
CodeMeaningRetry?
200 OKSuccessful read
202 AcceptedTest created, running asynchronously; follow Location or poll id
400 Bad RequestMalformed JSON or missing required fieldNo
401 UnauthorizedMissing or invalid Bearer keyNo
403 ForbiddenAuthenticated but not allowed (e.g., another account's test)No
404 Not FoundTest ID does not exist or purged past retentionNo
409 ConflictIdempotency collision with a different payload bodyNo
422 Unprocessable EntityWell-formed but rejected by domain logic (e.g., sender blocked)No
429 Too Many RequestsRate limit hitYes — read Retry-After
5xxUpstream or internal failureYes — exponential backoff

Pagination — cursor-based

Every list endpoint uses forward-only cursor pagination. You pass ?limit=N&cursor=..., and receive meta.nextCursor (or null) plus a Link header with rel="next".

GET /api/tests?limit=50

200 OK
Link: <https://check.live-direct-marketing.online/api/tests?limit=50&cursor=eyJpZCI6InRfMDEifQ==>; rel="next"

{
  "data": [ /* 50 test objects */ ],
  "error": null,
  "meta": {
    "nextCursor": "eyJpZCI6InRfMDEifQ==",
    "hasMore":    true,
    "requestId":  "req_..."
  }
}

Why cursor, not page?

Page-based pagination breaks when items are inserted or deleted mid-walk — you can either miss items or see duplicates. Cursor pagination is stable under concurrent writes and is the model every serious REST API has converged on over the last decade.

The cursor is opaque — do not try to base64-decode it or sort by it. Treat it as a "continue reading here" token and nothing more.

Test object schema

{
  "id":              "t_01H9XABC4D5E6F7G8H",
  "status":          "complete",
  "createdAt":       "2026-07-03T11:02:14Z",
  "completedAt":     "2026-07-03T11:03:42Z",
  "from":            "hello@news.yourbrand.com",
  "subject":         "Weekly digest #42",
  "summary": {
    "inboxCount":    18,
    "spamCount":      3,
    "missingCount":   1,
    "total":         22
  },
  "auth":            { "spf": "pass", "dkim": "pass", "dmarc": "pass" },
  "spamAssassinScore": 1.2,
  "rspamdScore":       0.8,
  "providers":       [ /* per-seed detail */ ],
  "dnsbl":           [ /* blacklist check results */ ],
  "screenshots":     [ /* screenshot URLs */ ],
  "metadata":        { /* opaque tenant metadata */ }
}

Provider result sub-object

{
  "provider":  "gmail",
  "folder":    "inbox",
  "folderRaw": "INBOX",
  "tookMs":    4120,
  "headersUrl": "https://check.live-direct-marketing.online/r/t_.../headers.txt",
  "screenshotUrl": "https://check.live-direct-marketing.online/r/t_.../gmail.png"
}

folder is normalised to a small enum: inbox, spam, promotions, updates, social, missing, other. folderRaw is the provider-native label for debugging.

SPF / DKIM / DMARC sub-object

{
  "spf": {
    "result": "pass",
    "record": "v=spf1 include:_spf.google.com include:sendgrid.net ~all",
    "domain": "news.yourbrand.com"
  },
  "dkim": {
    "result":   "pass",
    "selector": "s1",
    "domain":   "news.yourbrand.com",
    "keySize":  2048
  },
  "dmarc": {
    "result":   "pass",
    "policy":   "quarantine",
    "alignment": { "spf": "pass", "dkim": "pass" }
  }
}

DNSBL sub-object

[
  { "list": "zen.spamhaus.org",    "listed": false },
  { "list": "bl.spamcop.net",      "listed": false },
  { "list": "b.barracudacentral.org", "listed": false },
  { "list": "dnsbl.sorbs.net",     "listed": true, "reason": "dynamic IP range" }
]

Screenshot URL lifecycle

Screenshot URLs are signed and time-limited. They survive as long as the test is within its retention window (30 days on free, 12 months on Starter, 24 on Growth), then return 404. The URL does not change — there is no rotation — so you can bookmark or embed it as long as the test is in retention.

Do not hotlink screenshots into tenant UI long-term. Download them and rehost if you need longer retention than your plan provides.

Timestamps — ISO 8601, always UTC

Every timestamp the API returns is ISO 8601, in UTC, with a trailing Z. Example: 2026-07-03T11:02:14Z. No other format is ever returned; no timezone offsets other than Z; no epoch seconds; no microseconds.

Render in the user's local timezone on your frontend using whatever library you already have. Never assume a server-side format change.

JSON Schema fragment

Paste this into your OpenAPI generator. It covers the envelope and the test object with enum-validated fields.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id":     "https://check.live-direct-marketing.online/schema/test.json",
  "type":    "object",
  "required": ["data", "error", "meta"],
  "properties": {
    "data": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object",
          "required": ["id", "status", "createdAt"],
          "properties": {
            "id":         { "type": "string", "pattern": "^t_[A-Z0-9]{26}$" },
            "status":     { "enum": ["pending", "running", "complete", "cancelled", "failed"] },
            "createdAt":  { "type": "string", "format": "date-time" },
            "completedAt":{ "type": "string", "format": "date-time" },
            "from":       { "type": "string", "format": "email" },
            "subject":    { "type": "string", "maxLength": 998 },
            "summary": {
              "type": "object",
              "required": ["inboxCount", "spamCount", "missingCount", "total"],
              "properties": {
                "inboxCount":   { "type": "integer", "minimum": 0 },
                "spamCount":    { "type": "integer", "minimum": 0 },
                "missingCount": { "type": "integer", "minimum": 0 },
                "total":        { "type": "integer", "minimum": 0 }
              }
            },
            "auth": {
              "type": "object",
              "properties": {
                "spf":   { "enum": ["pass", "fail", "neutral", "softfail", "none"] },
                "dkim":  { "enum": ["pass", "fail", "none"] },
                "dmarc": { "enum": ["pass", "fail", "none"] }
              }
            }
          }
        }
      ]
    },
    "error": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object",
          "required": ["code", "message"],
          "properties": {
            "code":    { "type": "string" },
            "message": { "type": "string" },
            "field":   { "type": "string" },
            "docs":    { "type": "string", "format": "uri" }
          }
        }
      ]
    },
    "meta": {
      "type": "object",
      "required": ["requestId"],
      "properties": {
        "requestId":        { "type": "string" },
        "creditsRemaining": { "type": "integer" },
        "nextCursor":       { "type": "string" },
        "hasMore":          { "type": "boolean" },
        "version":          { "type": "string" }
      }
    }
  }
}
Version header

The meta.version field echoes the API version your request was handled under. Pin to a version by sending X-API-Version: 2026-07-01. Without the header you get the latest stable version, which can introduce additive changes (new fields) but never breaking ones.

Frequently asked questions

Will you ever change a response format?

We make additive changes freely — new fields, new status codes in the 2xx range. Breaking changes (removed fields, changed types, new required request fields) ship under a new X-API-Version. Old versions stay supported for at least 12 months after deprecation.

Why cursor pagination instead of offset?

Offset pagination gives you duplicate or skipped items when rows are inserted between page fetches. Cursor pagination is stable under concurrent writes. It also performs better on the database side, which matters once history grows.

Are status values stable enums I can switch on?

Yes. The status field is a closed enum: pending, running, complete, cancelled, failed. New values would require a new major API version. You can safely switch on it in your code.

Can I get JSON:API or HAL instead of this envelope?

No. We picked one envelope and stuck with it. JSON:API adds indirection most clients do not need; HAL's hypermedia links are neat but every client library ignores them. Our envelope is simple enough to wrap in any language in five minutes.
Related reading
Found this useful? Share it
AB
About the author
Artem Berezin
B2B Deliverability Specialist

B2B deliverability specialist with 5+ years of hands-on outreach experience. Built campaigns reaching 90,000+ inboxes across 20+ countries — and fixed the deliverability problems that came with that scale.

Check your deliverability across 20+ providers

Gmail, Outlook, Yahoo, Mail.ru, Yandex, GMX, ProtonMail and more. Real inbox screenshots, SPF/DKIM/DMARC, spam engine verdicts. Free, no signup.

Run Free Test →

Unlimited tests · 20+ seed mailboxes · Live results · No account required