开发者
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.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 1fetch(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 解析后即删