SenderGuard
SenderGuard

开发者

SenderGuard 仅开放两个只读接口(不访问邮箱正文,不触发退订链接,上传的 EML 解析后即删)。 请在请求头携带清晰的 User-Agent(如 acme-infra-monitor/1.1),便于出现异常时联系你。

速率限制
  • 匿名(按 IP):每分钟 5 次
  • 携带 API Key(x-api-key 或 Bearer):每分钟 30 次

遭遇 429 时会返回 Retry-After(秒)与 X-RateLimit-Remaining

可直接复制下方内容,或访问 /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.

示例代码(含 Retry-After 处理)

curl

遇到 HTTP 429 会按服务器要求的秒数休眠后重试。

#!/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 '连续 5 次请求均被限流,已退出' >&2
exit 1

fetch(Node)

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

Python requests

import os\nimport time\nimport requests\n\nAPI = "https://senderguard.com/api/verify"\nHEADERS = {"User-Agent": "acme-monitor/1.1"}\nif os.getenv("SG_API_KEY"):\n    HEADERS["x-api-key"] = os.environ["SG_API_KEY"]\n\nparams = {"scanId": "d3f4cedcab12abcd"}\nfor _ in range(5):\n    resp = requests.get(API, headers=HEADERS, params=params, timeout=30)\n    if resp.status_code != 429:\n        resp.raise_for_status()\n        print(resp.json())\n        break\n    retry = int(resp.headers.get("Retry-After", "10"))\n    time.sleep(retry)\nelse:\n    raise RuntimeError("Rate limit persisted after retries")

接下来怎么做?

需要更高突发或 Webhook?先在设置里生成 API Key,把响应集成到监控体系,并持续使用/scan/<id>#why-it-passed 校验哈希与差异块。

只读 · 不存正文 · EML 解析后即删