Docs navigation
REST API
Weather MCP
Agents as developers
Errors and rate limits
Errors are RFC 9457 problem documents, and a 401, 403, or 429 tells you what to do next in a next_steps member. Rate limits are per plan and advertised on every response.
The Smarter Weather REST API returns errors as RFC 9457
application/problem+json
documents (RFC 9457 obsoletes RFC 7807 with the same wire format). This
doc covers the canonical Problem shape, the next_steps extension
that tells you what to do about a 401, 403, or 429, the stable
error type URIs the platform emits, and how clients should react to
each class.
The Problem shape
Every 4xx or 5xx response body is a JSON object with:
| Field | Type | Required | Meaning |
|---|---|---|---|
type |
string | yes | Stable URI identifying the error class. Clients SHOULD switch on this. |
title |
string | yes | Short human-readable label. Safe to display to end users as-is. |
status |
number | yes | HTTP status code. Matches the response line. |
detail |
string | no | Free-form description with request-specific context. May change between releases. |
instance |
string | no | The request path the failure occurred on (e.g. /v1/weather). Pair it with the X-Request-Id response header in support tickets. |
Extension members beyond these five may appear (RFC 9457 §3.2); today
401, 402, 403, and 429 carry next_steps (below). Ignore members you
do not understand.
The Content-Type header on any error response is
application/problem+json, never application/json. Test
harnesses that sniff on Content-Type must accept both.
Example:
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 12
{
"type": "https://smarterweather.com/errors/too-many-requests",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Retry after 12 seconds.",
"instance": "/v1/weather"
}
next_steps: the error tells you what to do
A 401, 402, 403, or 429 is where a first
integration most often stalls, so those statuses carry a next_steps
extension member: a map of link relations, each { href, description }
plus optional grant fields, plus recommended naming the one to take
first. When present, if_no_human_present names the relation an
autonomous agent should take if nobody can act on recommended right
now. Relations today: device_flow (401 recommended; also carries
client_id, device_authorization_endpoint, token_endpoint,
api_keys_endpoint), get_key, quickstart, agents,
onboarding_mcp, keyless_x402, key_handling, errors (on 401;
403 recommends get_key), claim (on 402 and trial-route 403),
and upgrade, pricing, usage, errors (on 429). New relations
may be added; ignore unknown ones. Every href carries
utm_source=api&utm_medium=problem-json&utm_campaign=<status>. Grant
endpoints on device_flow do not get UTM. When a trial key's free value
ends, the response is 402 with next_steps.recommended = claim (see
ADR 071); the
per-key claim href uses a claim ticket in the URL fragment
(https://developers.smarterweather.com/claim#ticket=<ticket>).
Pre-ticket rows still use leftover #key=<bearer> until they TTL.
HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
WWW-Authenticate: Bearer realm="api.smarterweather.com"
{
"type": "https://smarterweather.com/errors/unauthorized",
"title": "Unauthorized",
"status": 401,
"detail": "Missing Authorization header. Pass your API key as 'Authorization: Bearer sw_live_*'.",
"instance": "/v1/weather",
"next_steps": {
"recommended": "device_flow",
"device_flow": {
"href": "https://developers.smarterweather.com/quickstart?utm_campaign=401&utm_medium=problem-json&utm_source=api#device-flow",
"description": "No install, no loopback port: POST device_authorization_endpoint, show the human the code, poll, then GET /developer/keys.",
"client_id": "k2h05BUoTP393zcD",
"device_authorization_endpoint": "https://clerk.smarterweather.com/oauth/device_authorization",
"token_endpoint": "https://clerk.smarterweather.com/oauth/token",
"api_keys_endpoint": "https://api.smarterweather.com/developer/keys"
},
"get_key": {
"href": "https://developers.smarterweather.com/dashboard/api-keys?utm_campaign=401&utm_medium=problem-json&utm_source=api",
"description": "Sign in (free, no card) and mint an API key. Pass it as 'Authorization: Bearer sw_live_*'."
},
"quickstart": { "href": "…", "description": "…" },
"agents": { "href": "…", "description": "…" },
"onboarding_mcp": { "href": "…", "description": "…" },
"keyless_x402": { "href": "…", "description": "…" },
"key_handling": { "href": "…", "description": "…" },
"errors": { "href": "…", "description": "…" }
}
}
401 and 403 also carry an RFC 6750
WWW-Authenticate: Bearer challenge. A request with no credentials gets
the bare realm (§3.1); otherwise error is invalid_request
(malformed header), invalid_token (unknown, revoked, or expired key),
or insufficient_scope (with scope naming what the route needs), and
error_uri points at this page.
Canonical error type URIs
The type URI prefix is https://smarterweather.com/errors/. These
URIs are stable: new error classes are added over time, but existing
URIs do not change meaning. Compare type as an opaque string --
don't fetch it at runtime. Each one redirects to its row below when a
human does open it.
Note that 401 is a single type. The API deliberately does not
distinguish "unknown key" from "revoked key" in the type URI, because
doing so would let an unauthenticated caller probe which key strings
were once valid. Branch on detail for user-facing copy, never for
control flow.
Retry guidance
| Error class | Retry? |
|---|---|
bad-request, unauthorized, forbidden, trial-ended, trial-route-not-allowed, not-found |
No |
too-many-requests, rate-limit-exceeded |
After Retry-After (or the RateLimit-Reset window). |
payload-too-large, flag-disabled |
No |
conflict |
Once, after re-reading state. |
internal, upstream, service-unavailable, timeout |
Exponential backoff, max 3 retries. |
Retries SHOULD use at least 250ms of initial backoff and double each attempt (up to ~2s cap) to avoid thundering-herd restarts after a transient upstream outage.
Including errors in support tickets
When contacting support, include the X-Request-Id response header
along with the instance path from the body. That request id resolves
to the full request trace on our side and cuts triage time from hours
to minutes.
Rate limits by plan
Limits come from the published API-tier registry. Every response also carries RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, and RateLimit-Policy. On 429, honor Retry-After before you send the next request.
| Plan | Per minute | Burst | Daily | Monthly |
|---|---|---|---|---|
| Free | 60 | 30 | 1,000 | — |
| Developer | 600 | 100 | 50,000 | 500,000 |
| Professional | 6,000 | 1,000 | 500,000 | 5,000,000 |