Skip to main content

legalize

python-ci PyPI Python versions License: MIT

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.typed ships in the wheel. mypy --strict clean.
  • Sync by default, async when you need it. Legalize and AsyncLegalize expose 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

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)

Source distribution for legalize 0.3.0
File Size Uploaded
legalize-0.3.0.tar.gz 27.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for legalize 0.3.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page