API documentation
With the Postwacht API you check your emails automatically – for example after every deployment or before every newsletter. The API is free and enabled on request.
Quick start
- Apply for access in your account under API. Once approved – usually within one business day – you create a key there.
- Create a test. You'll get back a one-time address:
curl -X POST https://postwacht.de/api/v1/tests \ -H "Authorization: Bearer pwk_…" {"code": "k7m2x9…", "address": "k7m2x9…@in.postwacht.de", "status": "waiting", …} - Send your email to this address – from your application, your newsletter tool or your mail server.
- Poll the result until
statusisdone(usually after 10 to 30 seconds):curl https://postwacht.de/api/v1/tests/k7m2x9… \ -H "Authorization: Bearer pwk_…"
Authentication and limits
Every request needs the header Authorization: Bearer <key>. A key has the same rights as your account. You can revoke it in your account at any time.
In your application you state which servers your application calls the API from (hostname, IP address or small network). Requests from other addresses are rejected – even with a valid key.
- At most 60 requests per minute and key
- 20 (Pro: 200) tests per day (the same quota as on the website)
- 5 (Pro: 50) inbox tests per day
Tests
| Call | Purpose |
|---|---|
POST /api/v1/tests | Create a new test. Response: code, address, status, report_url |
GET /api/v1/tests?limit=20 | Your latest tests |
GET /api/v1/tests/{code} | Result of a test with score, all checks and fix plan |
status is waiting (email not there yet), received, analyzing, done or failed. With done, the response contains score (0 to 10), results with every single check and fix_plan with the recommended actions.
Example of a check in a CI pipeline:
SCORE=$(curl -s https://postwacht.de/api/v1/tests/$CODE \ -H "Authorization: Bearer $POSTWACHT_KEY" | jq .score) [ "$(echo "$SCORE >= 9" | bc)" = 1 ] || exit 1
Titles and findings in responses are in German by default. Send the header Accept-Language: en to get them in English.
Domains and DMARC
| Call | Purpose |
|---|---|
GET /api/v1/domains | Monitored domains with status and DMARC report address |
POST /api/v1/domains | Monitor a domain. Body: {"domain": "example.com"} |
GET /api/v1/domains/{domain}?days=30 | Last check, DMARC analysis (volume, pass rate, senders, daily values) and TLS-RPT analysis |
Inbox test
| Call | Purpose |
|---|---|
POST /api/v1/inbox-tests | Start an inbox test. Response: code (put it in the subject or body) and addresses (recipients) |
POST /api/v1/inbox-tests/{code}/cancel | Cancel a running inbox test. If no mailbox has found the email yet, it doesn't count against the daily limit. |
GET /api/v1/inbox-tests/{code} | Folder per provider: inbox, promotions, spam, missing, cancelled or null (still checking) |
Webhooks
Instead of polling, you can have events sent to you. Add an HTTPS address under Account → API. Postwacht then sends a POST with JSON:
{"event": "test.done",
"created_at": "2026-10-11T09:03:56+00:00",
"data": {"code": "k7m2x9…", "status": "done", "score": 9.4,
"from_domain": "example.com", "report_url": "https://postwacht.de/t/k7m2x9…"}}
| Event | When | Content of data |
|---|---|---|
test.done | A test has been analyzed | code, status, score, subject, from_domain, report_url |
domain.changed | A result of the daily domain check has changed | domain, status, changes (check, before, after, details), url |
inbox_test.done | An inbox test has completed | code, results (provider and folder), url |
ping | You click “Send test” | message |
Verifying the signature
Every call carries the headers X-Postwacht-Event, X-Postwacht-Timestamp and X-Postwacht-Signature. The signature is an HMAC-SHA256 over <timestamp>.<body> with your secret. Verify it and discard calls whose timestamp is older than five minutes:
import hashlib, hmac, time
def is_genuine(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False
mac = hmac.new(secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256)
return hmac.compare_digest("sha256=" + mac.hexdigest(), signature)
Respond with a status between 200 and 299. For other responses or if your address is unreachable, Postwacht retries after 1, 5 and 30 minutes. After 20 consecutive failures the webhook is disabled; “Send test” turns it back on. Redirects are not followed, and only public addresses are called.
Error
Errors come as JSON with a matching HTTP status, e.g. {"error": "Daily limit of 20 tests reached"}.
| Status | Meaning |
|---|---|
401 | Key missing, wrong or revoked |
403 | API access not (or no longer) enabled, request from an address that isn't allowed, or account blocked |
404 | Test or domain doesn't exist (or doesn't belong to your account) |
409 | Maximum number of domains reached |
422 | Invalid input |
429 | Limit reached – for the per-minute limit, the Retry-After header says when you can continue |