Errors
HTTP status codes and the conditions that produce them. Consult this before retrying a failed request.
| Status | Meaning | Typical cause |
|---|---|---|
| 400 | validation failed | missing recipient, invalid email, placeholder email (example.com, your-email, etc.) |
| 410 | endpoint expired | 30-day TTL elapsed without submissions |
| 429 | rate limited or cap reached | >10 creations/hour/IP, or 50-submission lifetime cap reached |
Example 400
curl -i -X POST https://api.gopigeon.dev/new -d 'recipient=not-an-email' # HTTP/1.1 400 Bad Request # {"error":"validation failed","field":"recipient"}
Dry-run response (not an error)
Submissions sent with ?dry_run=1 always return HTTP 200 with a JSON body whose top-level field is "dry_run": true — there is no error key. If your client treats any 200 + non-empty JSON body as success, dry-run will look like a normal successful submission but the body shape is different.
Distinguishing key: the dry-run body’s top-level fields are dry_run, would_route_to, claim_status, validations_passed, would_have_counted. The real-submission body has ok, submission_id. The 400/410/429 error bodies have error, plus optionally field, code, _help. The three response shapes are disjoint — no single field name appears in more than one.
Minimal dry-run body excerpt:
{
"dry_run": true,
"would_route_to": [{"kind": "email", "target_hint": "u***@example.com"}],
"claim_status": {"claimed": false},
"would_have_counted": {"quota_used": 12, "quota_remaining": 488}
} Dry-run is a routing preview only — sends nothing. See /docs/curl for the full response contract and curl examples on both form and queue endpoints. The dry-run flag never produces a 4xx on its own; if the underlying request fails CORS, honeypot, or rate-limit, you’ll get the same 4xx you’d see in a real submission (because dry-run with ?dry_run=1 runs the full guard cascade). So a 403 with ?dry_run=1 means the same thing as a 403 without it — it just doesn’t consume any quota either way.
Retry guidance
- 400: fix the payload and re-send; the response body names the offending
field. - 410: create a new endpoint — expired endpoints cannot be resurrected.
- 429: back off and retry later. The
Retry-Afterheader, when present, is authoritative.