legalize
Official Python client for the Legalize API — legal texts as structured, versioned data.
pip install legalize
from legalize import Legalize
client = Legalize(api_key="leg_...")
for law in client.laws.iter(country="es", law_type="ley_organica"):
print(law.id, law.title)
Why this SDK
- Typed end-to-end. Pydantic v2 models generated from the canonical
OpenAPI spec.
py.typedships in the wheel.mypy --strictclean. - Sync by default, async when you need it.
LegalizeandAsyncLegalizeexpose the same resource API and error types — swap one for the other without rewriting your call sites. - Retries with backoff built in. Honors
Retry-After, handles 429/5xx, exponential delay with jitter, all configurable. - Webhook verification is a one-liner. Constant-time HMAC compare, 5-minute anti-replay window, clock-skew tolerant.
- No magic, no frameworks. One client, one method per endpoint.
Quick tour
List, iterate, search
# One page
page = client.laws.list(country="es", page=1, per_page=50)
print(page.total, len(page.items))
# Auto-paginated iterator (fetches pages as needed)
for law in client.laws.iter(country="es", status="vigente"):
...
# Full-text search
results = client.laws.search(country="es", q="protección de datos")
# ...page by page
page2 = client.laws.search(country="es", q="protección de datos", page=2)
# ...or every match, paginated for you
for law in client.laws.search_iter(country="es", q="protección de datos"):
...
Time-travel
Every law has a git-tracked history. Retrieve it at any past revision:
commits = client.laws.commits(country="es", law_id="ley_organica_3_2018")
past = client.laws.at_commit(
country="es",
law_id="ley_organica_3_2018",
sha=commits.items[-1].sha,
)
print(past.content) # Markdown at that revision
Or skip the SHA lookup entirely and ask by date:
at = client.laws.at_date(country="es", law_id="ley_organica_3_2018", date="2019-05-13")
print(at.sha, at.version_date) # which version answered, so you can cite it
The rule is published on or before the date, not in force on it.
XML (and other raw formats)
The typed methods always return JSON-parsed models. When your app speaks
XML, use request_raw to fetch any endpoint in another wire format via
content negotiation — it sets Accept and hands you the body untouched:
res = client.request_raw("GET", "/api/v1/es/laws/BOE-A-1978-31229")
res.content_type # "application/xml; charset=utf-8"
xml_text = res.text # the raw XML string
root = res.xml() # parsed into an ElementTree element
# format="json" or any explicit media type works the same way:
data = client.request_raw("GET", "/api/v1/countries", format="json").json()
request_raw defaults to format="xml". Errors raise the same typed
exceptions as the JSON methods (the error body is in the negotiated
format). See the
Response formats docs.
Async
import asyncio
from legalize import AsyncLegalize
async def main():
async with AsyncLegalize(api_key="leg_...") as client:
page = await client.laws.list(country="es")
async for law in client.laws.iter(country="fr"):
print(law.id)
asyncio.run(main())
Webhooks
Verify a signed delivery in one call:
from legalize import Webhook, WebhookVerificationError
try:
event = Webhook.verify(
payload=request.body, # raw bytes
sig_header=request.headers["X-Legalize-Signature"],
timestamp=request.headers["X-Legalize-Timestamp"],
secret=os.environ["LEGALIZE_WHSEC"],
)
except WebhookVerificationError:
return Response(status_code=400)
if event.type == "law.updated":
...
Working Flask and FastAPI receivers in
examples/.
Configuration
Zero-config (recommended for servers + Kubernetes)
Set the environment and just instantiate:
export LEGALIZE_API_KEY=leg_live_...
# Optional:
export LEGALIZE_BASE_URL=https://legalize.dev
export LEGALIZE_API_VERSION=v1
from legalize import Legalize
client = Legalize() # picks everything up from the environment
Explicit
from legalize import Legalize, RetryPolicy
client = Legalize(
api_key="leg_...",
base_url="https://legalize.dev",
api_version="v1", # negotiated via Legalize-API-Version
timeout=30.0,
retry=RetryPolicy(max_retries=5, initial_delay=0.5, max_delay=10.0),
default_headers={"X-Correlation-Id": "..."},
)
Precedence: explicit argument > environment variable > built-in default.
The full cross-SDK contract is documented in
ENVIRONMENT.md.
Read rate-limit headers from the last response:
client.countries.list()
resp = client.last_response
print(resp.headers.get("X-RateLimit-Remaining"))
Errors
All errors inherit from LegalizeError. Catch the specific one you care
about and let the rest bubble:
from legalize import (
AuthenticationError, # 401 — bad/missing key
ForbiddenError, # 403
NotFoundError, # 404
InvalidRequestError, # 400
ValidationError, # 422
RateLimitError, # 429 — retried automatically by default
ServerError, # 5xx
APIConnectionError, # network failure
APITimeoutError, # timeout
WebhookVerificationError,
)
Every APIError exposes .status_code, .code, .body, and
.response for debugging.
Compatibility
- Python 3.10, 3.11, 3.12, 3.13
- Linux, macOS, Windows
httpx≥ 0.27,pydantic≥ 2.6
Links
- API reference
- Monorepo (sources for all language SDKs)
- Changelog
- Contributing
License
MIT
Metadata
Release files for legalize 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| legalize-0.3.0.tar.gz | 27.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| legalize-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.6 kB
Release files / legalize-0.3.0.tar.gz
| Download URL | legalize-0.3.0.tar.gz |
|---|---|
| Size | 27.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7cccfd52165d3afe73ef5f1732321a40ceeacb61ed7c1d8a23661faf49ace7aa
|
|
BLAKE2b-256 checksum How to use checksums |
2247249c595be8091be4701de4462094fe29e47cb957aa3d30227e14d32a29fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.
Transparency logRelease files / legalize-0.3.0-py3-none-any.whl
| Download URL | legalize-0.3.0-py3-none-any.whl |
|---|---|
| Size | 31.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
37ddb98f442273b09b5051f2970cffe34a828cbcd39cb312e9ab1836ce0df5bb
|
|
BLAKE2b-256 checksum How to use checksums |
a4148985f3b948fac707f7e898185a7181b3f53eb6a6f45dcd8a712940937fe9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.
Transparency log