# Leya — Latin American legal research API > Leya is a search API over a corpus of primary Latin American legal sources: > statutes, decrees, congressional initiatives (iniciativas), court rulings > (sentencias), regulations, and official gazettes. It exists so an LLM can cite > real law instead of recalling it. Every result carries a stable document id, a > source attribution, a link to the official document, and corpus-freshness > metadata. Use Leya when a question needs the actual text of a Latin American legal instrument — a decree's articles, an initiative's current status, whether a statute was in force on a given date. Do not use it for jurisdictions it does not cover (it will return nothing rather than a neighbouring country's law), or for general legal theory untied to a corpus document. ## Coverage Total indexed: 41,779 documents. Last checked 2026-08-08T15:34:50.835761Z. ### By jurisdiction - República Dominicana (DO): 39,094 documents, documents dated 1844-02-28 to 2027-08-16 - Guatemala (GT): 2,685 documents, documents dated 1996-10-16 to 2027-01-01 ### By source (`source_id` values accepted by the `source_id` filter) - `do-camara-diputados` — Cámara de Diputados - Trámites Legislativos (DO, legislature): 676 documents - `do-gaceta-oficial` — Gaceta Oficial - Consultoría Jurídica del Poder Ejecutivo (DO, official_gazette): 29,389 documents - `do-scj` — Suprema Corte de Justicia - Sentencias (DO, supreme_court): 10 documents - `do-senado` — Senado - Actas e Iniciativas Aprobadas (DO, legislature): 570 documents - `do-tc` — Tribunal Constitucional - Sentencias (DO, constitutional_court): 8,449 documents - `gt-amm` — Administrador del Mercado Mayorista (GT, regulator): 30 documents - `gt-cnee` — Comisión Nacional de Energía Eléctrica (GT, regulator): 331 documents - `gt-congreso-iniciativas` — Congreso de la República - Iniciativas de Ley (GT, legislature): 1,364 documents - `gt-dca` — Diario de Centro América (GT, official_gazette): 960 documents `GET /api/corpus/status` is the authoritative, live answer. A jurisdiction it doesn't list is not in the corpus: Leya returns no results for it rather than substituting another country's law. ## Authentication Most callers use a bearer token: Authorization: Bearer leya_live_... Keys are self-serve at https://leya.lawyer/api-keys. `leya_test_` keys hit the same corpus and are for development. A key is not the only way in: any `/v1` read also accepts payment for that one request, with no account at all — see **Paying per call** below. The no-auth sandbox at https://leya.lawyer/demo is for humans evaluating the product. ## Endpoints Base URL: `https://api.leya.lawyer` ### GET /v1/search Query params: `q` (required), `country` (ISO-2, e.g. `GT`, `DO`), `source_id`, `document_type`, `legal_area`, `as_of` (YYYY-MM-DD, see below), `limit` (1–50, default 10). curl -H "Authorization: Bearer $LEYA_API_KEY" \ "https://api.leya.lawyer/v1/search?q=requisitos+matrimonio+civil&country=GT&limit=5" Returns `{query, results[], result_count, limit, truncated}`. Each result has `document_id`, `source_id`, `title`, `snippet` (~500 chars), `score`, `url`, `effective_from`, `effective_to`, `publication_date`, `source_last_indexed`. When `has_more` is true, pass `next_cursor` back as `cursor` — with `q` and every filter unchanged — to walk the rest. The cursor is opaque and only valid for the query that produced it; replaying it against different terms is a 422, not a wrong page. Keep paging until `has_more` is false before claiming you searched exhaustively. ### GET /v1/documents/{document_id} Full text of one document. Pass a `document_id` from a search result verbatim. Returns `body_markdown` plus `has_body` and `body_length`. `has_body: false` means the document is indexed but its text has not been extracted — that is not the same as an empty document, and it is not a reason to fill in the text from memory. Bodies are unbounded; a full statute can be very long. ### GET /v1/documents/{document_id}/citation A citation string for inline use. `style: "latam-standard"` gives the full ` , <Órgano Emisor> ()` form. `style: "title-fallback"` means the corpus lacks the type/number/issuing-body metadata, so the string is title plus id and the citation is partial. **Use `formatted` verbatim in either case.** Completing a partial citation from memory produces a fabricated authority, which is the specific failure this API exists to prevent. ### GET /v1/sources Registered datasets and the `source_id` values the search filter accepts. ### GET /v1/me Key introspection: the identity a key belongs to. ### GET /v1/filters The accepted values for `country`, `document_type` and `legal_area`, each with a document count. Search rejects a value that isn't in the corpus with a 422 that lists the valid ones, so an empty result set now means genuine no-coverage rather than a typo. (`source_id` is not validated this way — take those from `/v1/sources`.) ## Cadastral parcels (Guatemala) Separate from the document corpus: cadastral geometry from RIC (Registro de Información Catastral), Guatemala's cadastre. **This is not a property registry.** Parcels are keyed by RIC's *código catastral* (`ccc`, e.g. `18-04-09-03971`), which is not the Registro General de la Propiedad's *finca/folio/libro*, and RIC publishes no mapping between them. No owner, no title, no encumbrance, no purchase history is available here. If asked who owns a property, say Leya cannot answer that. ### GET /v1/parcels/{ccc} Returns `{parcel, coverage, overlaps[]}`. `parcel` carries `area_m2` and `perimeter_m` **as RIC reports them** (not recomputed), the polygon as GeoJSON in EPSG:4326, and `fetched_at` — cadastral data changes, so this is a dated snapshot, not a live read. `overlaps[]` is what makes this legally useful: the protected areas, sacred sites, archaeological sites, watersheds, water bodies and forestry-incentive zones the parcel intersects, each with `overlap_fraction` (0–1). A parcel wholly inside a national park and one grazing its boundary are different legal situations — read the fraction, don't treat any intersection as equivalent. ### GET /v1/parcels Lists parcels by `department_id` / `municipality_code`, cursor-paged like search. ### GET /v1/cadastre/coverage and /v1/cadastre/layers Per-department parcel counts, and the overlay layer keys. ### Coverage is partial, and absence is not evidence RIC has surveyed a minority of Guatemala. Nine departments publish **zero** parcels and Guatemala department publishes fourteen; the data is concentrated in Izabal, Alta Verapaz, Petén, Baja Verapaz and Zacapa. So a 404 here usually means *RIC has never surveyed this area*, not *this property does not exist*. Every `parcel_not_found` error carries a `coverage` object saying which it is — read it. Reporting that a property doesn't exist because a cadastral lookup missed is a serious error, and the API gives you exactly the information needed to avoid it. ## Worked example One command, no account, $0.02 — is a Guatemalan parcel inside a protected area? tempo request "https://api.leya.lawyer/v1/parcels/02-02-01-00001?include_geometry=false" Returns that the 376-hectare parcel sits **100%** inside the Sierra de las Minas reserve and in the Río Polochic watershed. The fraction is the point: a parcel grazing a boundary and one wholly inside are different legal situations. What this does **not** give you is the decree that declared the reserve. That instrument is not in the corpus — see **Coverage** above and https://leya.lawyer/examples. ## Paying per call, with no account You do not need an API key. Any `/v1` read accepts payment for that single request under the [Machine Payments Protocol](https://mpp.dev): call it with no credentials, get `402` and a `WWW-Authenticate: Payment` challenge naming the price, settle, retry. No signup, no key, no balance to manage. tempo request "https://api.leya.lawyer/v1/search?q=despido+injustificado&country=GT" A search costs $0.03 (3 credits), a document read $0.01. `/v1/sources`, `/v1/filters` and `/v1/cadastre/layers` are free and need no payment at all — read those first to decide whether the corpus covers your question before spending anything. Machine-readable pricing lives in the discovery document at `https://api.leya.lawyer/openapi.json` (`x-service-info` and per-operation `x-payment-info`), per `draft-payment-discovery-00`. **Which to use.** Pay per call for one-off or exploratory work — it is the lowest-friction path and nothing to set up. Get a key when you are making many calls (prepaid credits are the same price but one settlement instead of hundreds), or when you want usage history and a balance you can watch. `/v1/me` and `/v1/credits` are key-only: they describe an account, and a keyless caller does not have one. ## Credits Calls are prepaid in credits. A new account gets **1,000 free credits** on its first request — enough for roughly 300 searches — so you can evaluate without paying anything. | Endpoint | Credits | |---|---| | `GET /v1/search` | 3 | | `GET /v1/parcels/{ccc}`, `GET /v1/parcels` | 2 | | `GET /v1/documents/{id}`, `.../citation` | 1 | | `GET /v1/me`, `/v1/credits`, `/v1/sources`, `/v1/filters`, `/v1/cadastre/layers` | **0** | Search costs most because it is the only endpoint that spends money per call, on a query embedding. Introspection and top-up are free on purpose: an account with no credits must still be able to find out why and buy more. Every charged response carries `X-Credits-Charged`. `GET /v1/credits` returns your balance, the live price list and recent ledger activity — read it rather than assuming these numbers. A request that fails on our side (5xx) is refunded. ### Running out When the balance can't cover a call you get `402` with code `insufficient_credits`, and `error.credits` gives `balance`, `required`, `shortfall`, `top_up_usd` and `top_up_url`. **Do not retry a 402 on a backoff.** It is `retryable: false` — waiting clears a rate limit but never an empty balance. Top up, or tell the user you are out of credits. A retry loop here will spin forever. ### Buying credits `POST /v1/credits/purchase?credits=N` implements the [Machine Payments Protocol](https://mpp.dev) — an IETF-draft HTTP payment scheme (`draft-ryan-httpauth-payment`) co-authored by Stripe and Tempo. Credits are $0.01 each ($10 per 1,000), settled in stablecoins on Tempo. The flow is one request cycle: call it, get `402` with a `WWW-Authenticate: Payment` challenge naming the price, settle, retry with `Authorization: Payment `. Any MPP client does this transparently — e.g. `tempo request`, or `mppx.fetch` in JS. Because the payment scheme owns the `Authorization` header, **this one route takes your API key in `X-Leya-Api-Key` instead** — it names the account the credits land on. tempo request -X POST \ "https://api.leya.lawyer/v1/credits/purchase?credits=1000" \ -H "X-Leya-Api-Key: $LEYA_API_KEY" Purchases are idempotent per settlement: replaying a credential does not buy twice. ## Errors Every `/v1` error is `{"error": {"code", "message", "status", "retryable", "docs"}}`. Branch on `code`, never on the message text. - `unauthenticated` (401) — missing, malformed, or revoked key. Not retryable. - `not_found` (404) — no such document. Re-search; don't invent the document. - `parcel_not_found` (404) — no such cadastral parcel. Read the `coverage` object before concluding anything: it usually means unsurveyed, not nonexistent. - `insufficient_credits` (402) — out of credits. **Not retryable**; top up or stop. `error.credits` carries the shortfall and where to buy more. - `payment_required` (402, on `/v1/credits/purchase` only) — this is the normal first step of the MPP flow, not a failure. Settle the challenge in `WWW-Authenticate` and retry. - `invalid_request` (422) — bad parameter. `fields[]` names each one. - `method_not_allowed` (405) — wrong HTTP method for that path. Not retryable. - `rate_limited` (429) — honour `Retry-After` (seconds), then retry once. - `internal_error` / `unavailable` (5xx) — retryable once. `retryable` tells you whether repeating the identical request could succeed. Successful responses carry `X-RateLimit-Limit` and `X-RateLimit-Window` so you can pace yourself before being rejected. ### as_of `as_of` excludes documents the corpus *knows* were outside force on that date: one whose `effective_from` is later, or whose `effective_to` is earlier. It does **not** guarantee everything returned was in force — a document with a null `effective_from` is kept, because an unknown date can't be ruled in or out, and most of the corpus has none. Check each result's `effective_from` and say the date is unknown when it's null. ## Dates are often unknown Most documents have a null `effective_from`: the corpus does not know when they entered into force. Treat null as *unknown*, never as *today* or *undated*, and say so when a date is load-bearing for the answer. When a date is present, `effective_from_basis` says where it came from: - `stated_date` — the document names the date. - `on_publication` / `days_after_publication` — read off the document's own vigencia clause ("entra en vigencia al día siguiente de su publicación") plus its publication date. - `human_verified` — a Leya reviewer confirmed it against the document. - `model_unverified` — **treat with suspicion.** The extraction model supplied this date for a document whose text states none, typically by reusing the document or publication date. On a graded sample it did that for every such document. Do not present it as the date the document establishes; say the effective date is unconfirmed. Everything except `model_unverified` reflects the document itself. Leya does not apply a jurisdiction's default vigencia rule: inferring a date the document never states is a legal judgement, so those documents stay null rather than getting a plausible guess. ## Not legal advice Output built on Leya is a draft for attorney review. Cite every claim to a document you actually retrieved, and surface coverage gaps rather than papering over them. ## More - API reference: https://leya.lawyer/docs/api - Full context for agents: https://leya.lawyer/llms-full.txt - OpenAPI spec: https://api.leya.lawyer/openapi.public.json - Live corpus coverage: https://leya.lawyer/status - Claude Code plugin: `/plugin marketplace add sebasibarguen/leya-plugin` - Contact: hello@leya.lawyer