code is the stable contract — branch on it, not on message wording.
details is optional context (offending field, retry_after seconds, …).
Error codes
| code | HTTP | When |
|---|---|---|
UNAUTHORIZED | 401 | Missing, invalid, revoked, or expired key; or the key’s account is no longer active |
FORBIDDEN | 403 | Reserved for future scopes (not currently returned) |
NOT_FOUND | 404 | Resource doesn’t exist, unknown handle, hidden by privacy settings, or removed by moderation — existence is never leaked |
INVALID_ARGUMENT | 400 | Parameter validation failed (details.field names it) |
RATE_LIMITED | 429 | Over quota — see Rate limits |
SERVICE_DISABLED | 503 | The Open API is temporarily switched off (maintenance/emergency) |
INTERNAL | 500 | Our bug — retry once, then back off |
Real examples
401 — no key (captured live):401 responses carry a
WWW-Authenticate: Bearer realm="seesaw-open-api"
header. Depending on the network path it may arrive under the name
x-amzn-remapped-www-authenticate — match either when debugging.404 semantics worth knowing
A 404 does not distinguish between “never existed”, “hidden by the owner’s privacy settings”, and “removed by moderation” — deliberately. Don’t retry 404s; surface them as “not available”. Over MCP, errors arrive as tool errors with the same message text (e.g.resource not found), so agents get the same signal.