Errors & retries

Every non-2xx response is JSON with error (machine code) and message (human text). Rate-limited responses carry a Retry-After header. The SDKs retry automatically on 429 and 5xx; POST and PATCH are never auto-retried by default so writes don't duplicate.

HTTP status codes

Status Error class Meaning
400InvalidRequestErrorMalformed parameter
401AuthenticationErrorMissing or invalid API key
403ForbiddenErrorFeature not available for your tier
404NotFoundErrorCountry, law, or webhook not found
422ValidationErrorBody failed schema validation
429RateLimitErrorMonthly quota exceeded; Retry-After set
503ServiceUnavailableErrorTransient overload
5xxServerErrorBug on our side; please retry

Handling them in the SDK

from legalize import NotFoundError, RateLimitError, APIError try: law = client.laws.retrieve("es", "BOE-A-1978-31229") except NotFoundError: return "No such law" except RateLimitError as e: logger.warning("rate limited; server asked for %s s", e.retry_after) raise except APIError as e: logger.error("legalize %d %s", e.status_code, e.message) raise
import { NotFoundError, RateLimitError, APIError } from "@legalize-dev/sdk"; try { const law = await client.laws.retrieve("es", "BOE-A-1978-31229"); } catch (err) { if (err instanceof NotFoundError) return "No such law"; if (err instanceof RateLimitError) logger.warn("rate limited", err.retryAfter); if (err instanceof APIError) logger.error(err.statusCode, err.message); throw err; }
law, err := client.Laws().Retrieve(ctx, "es", "BOE-A-1978-31229") if err != nil { var nf *legalize.NotFoundError var rl *legalize.RateLimitError var apiErr *legalize.APIError switch { case errors.As(err, &nf): return "No such law" case errors.As(err, &rl): log.Printf("rate limited: %v", rl.RetryAfter) case errors.As(err, &apiErr): log.Printf("%d %s", apiErr.StatusCode, apiErr.Message) } return err }

Retry semantics

Every SDK retries on 429 and 5xx with exponential backoff + full jitter, honors Retry-After (both integer-seconds and HTTP-date forms), and caps the total attempts at max_retries (default 3).

POST and PATCH are not retried by default. A failed webhooks.create that quietly retried on a 500 could leave two webhook endpoints behind. If you've verified that a specific POST is idempotent, opt in per-policy:

python
from legalize import Legalize, RetryPolicy client = Legalize(retry=RetryPolicy(retry_non_idempotent=True))

Rolling your own

If you can't use the SDK: retry only 429/5xx, delay = min(2^n + random(), 60), cap at 4 attempts, stop on any 4xx other than 429, and never retry POST/PATCH unless the endpoint is documented as idempotent.