SenderGuard

Developers

SenderGuard exposes two read-only endpoints—no inbox access, no unsubscribe callbacks, EML discarded after parsing. Include a descriptive User-Agent (e.g. acme-infra-monitor/1.1) so we can reach you if something looks off.

Rate limits
  • Anonymous (per IP): 5 requests / minute
  • API key (x-api-key or Bearer): 30 requests / minute

Every 429 includes Retry-After (seconds) and X-RateLimit-Remaining.

Copy the schema below or pull it directly from /openapi.yaml.

openapi: 3.1.0
info:
  title: SenderGuard Public API
  version: "1.0.0"
  description: |
    Read-only endpoints for retrieving SenderGuard audit summaries and verification proof.
    Anonymous access is limited to 5 requests per minute per IP. Authenticated access with
    an API key allows up to 30 requests per minute. Include a descriptive User-Agent string
    with contact information whenever possible.
servers:
  - url: https://senderguard.com
    description: Production
paths:
  /api/audit:
    get:
      summary: Fetch the latest audit for a domain
      description: Returns the newest stored audit (score, badges, identifiers) for the requested domain.
      parameters:
        - name: domain
          in: query
          description: Fully qualified domain name to lookup.
          required: true
          schema:
            type: string
            example: example.com
      responses:
        '200':
          description: Audit summary for the domain
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditResponse'
        '400':
          description: Invalid domain supplied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No audit found for the domain yet
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
            X-RateLimit-Remaining:
              description: Remaining quota in the current window.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/verify:
    get:
      summary: Verify a stored scan proof
      description: Replays the stored SenderGuard scan proof and returns verification metadata for assessment.
      parameters:
        - name: scanId
          in: query
          description: Identifier returned by the audit endpoint or dashboard.
          required: true
          schema:
            type: string
            example: d3f4cedcab12abcd
      responses:
        '200':
          description: Verification bundle for the supplied scanId
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifyResponse'
        '404':
          description: Scan proof not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/admin/rulepack:
    post:
      summary: Upload or update a RulePack (admin)
      description: |
        Creates or updates a RulePack by version. Requires an admin session email to match `ADMIN_EMAILS`,
        or an `x-admin-email` header when `ALLOW_ADMIN_HEADER=1` is set. Rollout percent defaults to 100.
      parameters:
        - name: x-admin-email
          in: header
          required: false
          description: Admin email (header override enabled only when ALLOW_ADMIN_HEADER=1).
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [version, json]
              properties:
                version:
                  type: string
                  example: v1
                json:
                  description: RulePack JSON payload used by the scorer
                  type: object
                rolloutPercent:
                  type: integer
                  minimum: 0
                  maximum: 100
                  example: 100
      responses:
        '200':
          description: RulePack stored
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: true
                  rulepack:
                    $ref: '#/components/schemas/RulePack'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden (admin only)
        '429':
          description: Rate limit exceeded
          headers:
            Retry-After:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
  /api/admin/rulepack/activate:
    post:
      summary: Activate a RulePack version (admin)
      description: Activates the specified RulePack and optionally sets a rollout percent (bucketed by seed).
      parameters:
        - name: x-admin-email
          in: header
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [version]
              properties:
                version:
                  type: string
                  example: v2
                rolloutPercent:
                  type: integer
                  minimum: 0
                  maximum: 100
                  example: 20
      responses:
        '200':
          description: Activated RulePack
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  rulepack:
                    $ref: '#/components/schemas/RulePack'
        '401': { description: Unauthorized }
        '403': { description: Forbidden (admin only) }
        '404': { description: RulePack not found }
  /api/admin/rulepack/rollback:
    post:
      summary: Roll back to a previous RulePack (admin)
      description: |
        Rolls back to a specific version or to the previous snapshot when `to=prev` is supplied.
      parameters:
        - name: x-admin-email
          in: header
          required: false
          schema:
            type: string
        - name: to
          in: query
          required: false
          schema:
            type: string
            example: prev
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                toVersion:
                  type: string
                  example: v1
      responses:
        '200':
          description: Rolled back successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  rulepack:
                    $ref: '#/components/schemas/RulePack'
        '401': { description: Unauthorized }
        '403': { description: Forbidden (admin only) }
        '404': { description: RulePack not found }
  /api/admin/rulepack/current:
    get:
      summary: Fetch current RulePack snapshot (admin)
      description: Returns the active RulePack, control (previous), and recent history. Supports ETag.
      parameters:
        - name: x-admin-email
          in: header
          required: false
          schema:
            type: string
        - name: If-None-Match
          in: header
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Current snapshot
          headers:
            ETag:
              description: Snapshot hash for conditional requests.
              schema:
                type: string
            Cache-Control:
              schema:
                type: string
                example: no-store
          content:
            application/json:
              schema:
                type: object
                properties:
                  version: { type: string, nullable: true }
                  rolloutPercent: { type: integer }
                  active: { $ref: '#/components/schemas/RulePack' }
                  control: { $ref: '#/components/schemas/RulePack' }
                  history:
                    type: array
                    items:
                      $ref: '#/components/schemas/RulePack'
                  etag: { type: string }
        '304': { description: Not Modified }
        '401': { description: Unauthorized }
        '403': { description: Forbidden (admin only) }
        '429':
          description: Rate limit exceeded
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
            X-RateLimit-Remaining:
              description: Remaining quota in the current window.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    AuditResponse:
      type: object
      properties:
        domain:
          type: string
          description: Domain that was audited.
          example: example.com
        score:
          type: integer
          description: Composite score across SPF, DKIM, DMARC, alignment, and unsubscribe checks.
          example: 94
        badges:
          type: array
          description: Pass/fail state for the five compliance badges.
          items:
            $ref: '#/components/schemas/Badge'
        scanId:
          type: string
          description: Identifier for verifying or exporting the audit.
          example: d3f4cedcab12abcd
        sha256:
          type: string
          description: Canonical hash of the audit payload.
          example: 8c53afbaf7ff853bd323df8b7861c12d732a94751b3254a5d6d6b1b6492a6bbe
        rulepackVersion:
          type: string
          description: Active rule pack version when the audit ran.
          example: demo
        suggestions:
          type: array
          description: Top recommendations captured with the audit.
          items:
            type: string
      required:
        - score
        - badges
        - scanId
        - sha256
    VerifyResponse:
      type: object
      properties:
        domain:
          type: string
          description: Domain associated with the scan proof.
        matched:
          type: boolean
          nullable: true
          description: Whether the recomputed hash matches the stored baseline (null when baseline unavailable).
        scanId:
          type: string
          example: d3f4cedcab12abcd
        sha256:
          type: string
          description: Recomputed hash of the stored result.
        rulepackVersion:
          type: string
          description: Rule pack version recorded with the scan.
        diffs:
          type: array
          description: Material differences recorded since the previous scan.
          items:
            type: string
        resolvers:
          type: array
          description: DNS resolvers used when recomputing the proof.
          items:
            type: string
      required:
        - scanId
        - sha256
        - diffs
        - resolvers
    Badge:
      type: object
      properties:
        label:
          type: string
          example: SPF
        status:
          type: string
          enum: [pass, warn]
          example: pass
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          example: rate_limited
        retryAfter:
          type: integer
          description: Only present when the request hit a rate limit.
    RulePack:
      type: object
      properties:
        version:
          type: string
        active:
          type: boolean
        rolloutPercent:
          type: integer
        createdAt:
          type: string
          format: date-time
        activatedAt:
          type: string
          format: date-time
          nullable: true
        summary:
          type: string
          nullable: true
        json:
          type: object
          description: Included on some endpoints to echo stored rulepack payload.

Examples with Retry-After handling

curl

Loops on HTTP 429 and sleeps for the advertised seconds.

#!/usr/bin/env bash
set -euo pipefail
API="https://senderguard.com/api/audit?domain=example.com"
UA="acme-monitor/1.1"
KEY="${SG_API_KEY:-}"
for attempt in {1..5}; do
  response=$(mktemp)
  body=$(mktemp)
  curl -sS -D "$response" -o "$body" \
    -H "User-Agent: $UA" \
    $([ -n "$KEY" ] && echo -H "x-api-key: $KEY") \
    "$API"
  status=$(awk 'NR==1 {print $2}' "$response")
  if [ "$status" != "429" ]; then
    cat "$body" && rm "$response" "$body"
    exit 0
  fi
  retry=$(awk 'tolower($1)=="retry-after:" {print $2}' "$response")
  sleep "${retry:-10}"
done
echo 'Gave up after 5 attempts' >&2
exit 1

fetch (Node)

import fetch from "node-fetch";

async function getAudit(domain, apiKey) {
  const url = new URL("https://senderguard.com/api/audit");
  url.searchParams.set("domain", domain);
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const res = await fetch(url, {
      headers: {
        "User-Agent": "acme-monitor/1.1",
        ...(apiKey ? { "x-api-key": apiKey } : {}),
      },
    });
    if (res.status !== 429) {
      if (!res.ok) throw new Error(`Failed: ${res.status}`);
      return res.json();
    }
    const retry = Number(res.headers.get("retry-after")) || 10;
    await new Promise((resolve) => setTimeout(resolve, retry * 1000));
  }
  throw new Error("Rate limit persisted after retries");
}

getAudit("example.com", process.env.SG_API_KEY).then(console.log);

Python requests

import os
import time
import requests

API = "https://senderguard.com/api/verify"
HEADERS = {"User-Agent": "acme-monitor/1.1"}
if os.getenv("SG_API_KEY"):
    HEADERS["x-api-key"] = os.environ["SG_API_KEY"]

params = {"scanId": "d3f4cedcab12abcd"}
for _ in range(5):
    resp = requests.get(API, headers=HEADERS, params=params, timeout=30)
    if resp.status_code != 429:
        resp.raise_for_status()
        print(resp.json())
        break
    retry = int(resp.headers.get("Retry-After", "10"))
    time.sleep(retry)
else:
    raise RuntimeError("Rate limit persisted after retries")

Next steps

Need higher bursts or webhook delivery? Generate an API key inside Settings, wire the response into your monitoring, and keep verifying hashes with /scan/<id>#why-it-passed.

Read-only · No email bodies · EML discarded after parsing