{"openapi":"3.0.3","info":{"title":"Ley Abierta API","description":"REST API for consolidated Spanish legislation. Source: Agencia Estatal BOE.\n\n## Versioning and deprecation\n\nThe API is versioned in the URL path (`/v1`). Within `/v1`, additive, backward-compatible changes (new endpoints, new optional fields) ship without notice. A breaking change (removing/renaming a field, changing a status code's meaning) either goes out as a new `/v2` served alongside `/v1`, or the affected `/v1` endpoint is deprecated first.\n\nA deprecated endpoint is announced on [GitHub Releases](https://github.com/leyabierta/leyabierta/releases) and [Discussions](https://github.com/leyabierta/leyabierta/discussions) with at least 90 days' notice before removal, and during that window its responses carry a `Deprecation: true` header (RFC 8594) plus a `Sunset: <date>` header (RFC 9745) naming the removal date. No `/v1` endpoint is deprecated today.\n\nSee also [`/llms.txt`](https://leyabierta.es/llms.txt) for an agent-oriented summary and [`/desarrolladores/`](https://leyabierta.es/desarrolladores/) for the full developer guide (rate limits, errors, quickstart).","version":"0.1.0","contact":{"name":"Ley Abierta","url":"https://github.com/leyabierta/leyabierta"},"license":{"name":"AGPL-3.0","url":"https://github.com/leyabierta/leyabierta/blob/main/LICENSE"}},"servers":[{"url":"https://api.leyabierta.es","description":"Producción"}],"tags":[{"name":"Leyes","description":"Search, detail, versions, diff, and references for laws"},{"name":"Reformas","description":"Personal reforms, public changelog, and reform details"},{"name":"Ómnibus","description":"Omnibus law detection with per-topic breakdowns"},{"name":"Preguntas","description":"Ask questions about Spanish legislation in plain language"},{"name":"Sistema","description":"Health checks and internal endpoints"}],"paths":{"/v1/laws":{"get":{"parameters":[{"schema":{"type":"string"},"in":"query","name":"q","required":false},{"schema":{"type":"string"},"in":"query","name":"country","required":false},{"schema":{"type":"string"},"in":"query","name":"jurisdiction","required":false},{"schema":{"type":"string"},"in":"query","name":"rank","required":false},{"schema":{"type":"string"},"in":"query","name":"status","required":false},{"schema":{"type":"string"},"in":"query","name":"materia","required":false},{"schema":{"type":"string"},"in":"query","name":"citizen_tag","required":false},{"schema":{"anyOf":[{"format":"numeric","default":0,"type":"string"},{"type":"number"}]},"in":"query","name":"limit","required":false},{"schema":{"anyOf":[{"format":"numeric","default":0,"type":"string"},{"type":"number"}]},"in":"query","name":"offset","required":false},{"schema":{"type":"string"},"in":"query","name":"sort","required":false}],"operationId":"getV1Laws","summary":"Search and list laws","description":"Full-text search and filtered listing of consolidated laws. Supports pagination, filtering by country, rank, status, materia, and citizen tag.","tags":["Leyes"],"responses":{"200":{"description":"Matching laws, paginated.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"BOE-A-1978-31229"},"title":{"type":"string"},"short_title":{"type":"string"},"country":{"type":"string","example":"es"},"jurisdiction":{"type":"string","example":"es"},"rank":{"type":"string","example":"ley"},"published_at":{"type":"string","format":"date"},"updated_at":{"type":"string","format":"date","nullable":true},"status":{"type":"string","enum":["vigente","derogada"]},"department":{"type":"string"},"source_url":{"type":"string","format":"uri"},"citizen_summary":{"type":"string"}}}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"},"capped":{"type":"boolean","description":"true when the underlying count was capped for performance."}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Hybrid search unavailable (OPENROUTER_API_KEY not configured, or the embedding/vector backend failed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/laws/{id}":{"get":{"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"operationId":"getV1LawsById","summary":"Get law detail","description":"Returns full law metadata, reforms, citizen tags, and structural blocks.","tags":["Leyes"],"responses":{"200":{"description":"Full law: metadata, reforms, citizen tags, and blocks.","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"id":{"type":"string","example":"BOE-A-1978-31229"},"title":{"type":"string"},"short_title":{"type":"string"},"country":{"type":"string","example":"es"},"jurisdiction":{"type":"string","example":"es"},"rank":{"type":"string","example":"ley"},"published_at":{"type":"string","format":"date"},"updated_at":{"type":"string","format":"date","nullable":true},"status":{"type":"string","enum":["vigente","derogada"]},"department":{"type":"string"},"source_url":{"type":"string","format":"uri"},"citizen_summary":{"type":"string"}}},{"type":"object","properties":{"citizen_tags":{"type":"array","items":{"type":"string"}},"reforms":{"type":"array","items":{"type":"object","properties":{"norm_id":{"type":"string"},"date":{"type":"string","format":"date"},"source_id":{"type":"string"},"affected_blocks":{"type":"array","items":{"type":"string"}}}}},"blocks":{"type":"array","items":{"type":"object","properties":{"block_id":{"type":"string","example":"a1"},"block_type":{"type":"string","example":"precepto"},"title":{"type":"string"},"position":{"type":"integer"},"current_text":{"type":"string"},"citizen_summary":{"type":"string","nullable":true}}}}}}]}}}},"404":{"description":"No law with this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/laws/{id}/summaries":{"get":{"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"operationId":"getV1LawsByIdSummaries","summary":"Get citizen summaries for all articles","description":"Returns a lightweight map of article title → citizen summary. Triggers background generation for missing summaries.","tags":["Leyes"],"responses":{"200":{"description":"Map of article title → citizen summary (only articles with a summary today).","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"example":{"Artículo 1":"España es un Estado social y democrático de Derecho…"}}}}},"404":{"description":"No law with this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/laws/{id}/history":{"get":{"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"operationId":"getV1LawsByIdHistory","summary":"Get reform history","description":"Returns the full reform timeline for a law, including which blocks were affected by each reform.","tags":["Leyes"],"responses":{"200":{"description":"Reform timeline with affected block titles.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"total_reforms":{"type":"integer"},"reforms":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"source_id":{"type":"string"},"affected_blocks":{"type":"array","items":{"type":"object","properties":{"block_id":{"type":"string"},"title":{"type":"string"}}}}}}}}}}}},"404":{"description":"No law with this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/laws/{id}/versions/{date}":{"get":{"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"date","required":true}],"operationId":"getV1LawsByIdVersionsByDate","summary":"Get law version at date","description":"Returns the full Markdown content of a law as it was on a specific date.","tags":["Leyes"],"responses":{"200":{"description":"Markdown content of the law as of the given date.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"date":{"type":"string","format":"date"},"content":{"type":"string","description":"Full Markdown text at that date."}}}}}},"400":{"description":"Invalid date format (must be YYYY-MM-DD).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No law with this id, or no version at this date.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/laws/{id}/markdown":{"get":{"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"operationId":"getV1LawsByIdMarkdown","summary":"Get current law Markdown","description":"Returns the full canonical Markdown of a law (frontmatter + text) as text/markdown. Machine-readable source for AI agents.","tags":["Leyes"],"responses":{"200":{"description":"Current canonical Markdown (frontmatter + text).","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No law with this id, or no Markdown available.","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/laws/{id}/diff":{"get":{"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"query","name":"from","required":false},{"schema":{"type":"string"},"in":"query","name":"to","required":false}],"operationId":"getV1LawsByIdDiff","summary":"Diff between two dates","description":"Returns a unified diff of a law between two dates. Both 'from' and 'to' query params are required (YYYY-MM-DD).","tags":["Leyes"],"responses":{"200":{"description":"Unified diff between the two dates.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"diff":{"type":"string","description":"Unified diff text (git diff format)."}}}}}},"400":{"description":"Missing or invalid 'from'/'to' query params.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No law with this id, or no diff computable for these dates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/laws/{id}/analisis":{"get":{"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"operationId":"getV1LawsByIdAnalisis","summary":"Get law analysis","description":"Returns materias, notas, and cross-references for a law. Falls back to BOE API if not cached locally.","tags":["Leyes"],"responses":{"200":{"description":"Materias, notas, and cross-references.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"materias":{"type":"array","items":{"type":"string"}},"notas":{"type":"array","items":{"type":"string"}},"referencias":{"type":"object","properties":{"anteriores":{"type":"array","items":{"type":"object","properties":{"relation":{"type":"string"},"normId":{"type":"string"},"text":{"type":"string"}}}},"posteriores":{"type":"array","items":{"type":"object","properties":{"relation":{"type":"string"},"normId":{"type":"string"},"text":{"type":"string"}}}}}}}}}}},"404":{"description":"No law with this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/laws/{id}/graph":{"get":{"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"operationId":"getV1LawsByIdGraph","summary":"Get relationship graph","description":"Returns graph nodes and edges representing cross-references for a law.","tags":["Leyes"],"responses":{"200":{"description":"Relationship graph nodes and edges for the law.","content":{"application/json":{"schema":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"rank":{"type":"string"}}}},"edges":{"type":"array","items":{}}}}}}},"404":{"description":"No law with this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/ranks":{"get":{"operationId":"getV1Ranks","summary":"List rank types","description":"Returns all legislative rank types with law counts.","tags":["Leyes"],"responses":{"200":{"description":"Rank types with law counts.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"rank":{"type":"string"},"count":{"type":"integer"}}}}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/materias":{"get":{"operationId":"getV1Materias","summary":"List subject categories","description":"Returns all materia (subject) categories with law counts.","tags":["Leyes"],"responses":{"200":{"description":"Subject categories with law counts.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"materia":{"type":"string"},"count":{"type":"integer"}}}}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/citizen-tags":{"get":{"parameters":[{"schema":{"anyOf":[{"format":"numeric","default":0,"type":"string"},{"type":"number"}]},"in":"query","name":"limit","required":false}],"operationId":"getV1Citizen-tags","summary":"List citizen tags","description":"Returns citizen-friendly tag categories with law counts.","tags":["Leyes"],"responses":{"200":{"description":"Citizen-friendly tag categories with law counts.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"tag":{"type":"string"},"count":{"type":"integer"}}}}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/stats":{"get":{"operationId":"getV1Stats","summary":"Global statistics","description":"Returns aggregate statistics: total laws, reforms, jurisdictions, etc.","tags":["Leyes"],"responses":{"200":{"description":"Aggregate statistics: total laws, articles, versions, reforms, categories, and date range.","content":{"application/json":{"schema":{"type":"object","properties":{"norms":{"type":"integer"},"articles":{"type":"integer"},"versions":{"type":"integer"},"reforms":{"type":"integer"},"categories":{"type":"integer"},"oldest":{"type":"string","format":"date"},"newest":{"type":"string","format":"date"}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/most-reformed":{"get":{"operationId":"getV1Most-reformed","summary":"Most reformed laws","description":"Returns the top 10 most frequently reformed laws.","tags":["Leyes"],"responses":{"200":{"description":"The 10 most frequently reformed laws.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"rank":{"type":"string"},"reform_count":{"type":"integer"},"published_at":{"type":"string","format":"date"}}}}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/jurisdictions":{"get":{"operationId":"getV1Jurisdictions","summary":"List jurisdictions","description":"Returns all jurisdictions (state + autonomous communities) with law counts.","tags":["Leyes"],"responses":{"200":{"description":"All jurisdictions (state + autonomous communities) with law counts.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"jurisdiction":{"type":"string"},"count":{"type":"integer"}}}}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/recent-reforms":{"get":{"operationId":"getV1Recent-reforms","summary":"Recent reforms","description":"Returns the 10 most recently reformed laws.","tags":["Leyes"],"responses":{"200":{"description":"The 10 most recently reformed laws.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"last_reform":{"type":"string","format":"date"},"citizen_summary":{"type":"string","nullable":true}}}}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/reforms/personal":{"get":{"parameters":[{"schema":{"type":"string"},"in":"query","name":"w","required":false},{"schema":{"type":"string"},"in":"query","name":"s","required":false},{"schema":{"type":"string"},"in":"query","name":"h","required":false},{"schema":{"type":"string"},"in":"query","name":"j","required":false},{"schema":{"type":"string"},"in":"query","name":"f","required":false},{"schema":{"type":"string"},"in":"query","name":"x","required":false},{"schema":{"type":"string"},"in":"query","name":"materias","required":false},{"schema":{"type":"string"},"in":"query","name":"jurisdiccion","required":false},{"description":"Alias of jurisdiccion","schema":{"type":"string"},"in":"query","name":"jurisdiction","required":false},{"schema":{"type":"string"},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"query","name":"offset","required":false}],"operationId":"getV1ReformsPersonal","summary":"Personal reforms feed","description":"Returns recent reforms filtered by the user's materias and jurisdiction. Accepts wizard answer params or raw materias CSV.","tags":["Reformas"],"responses":{"200":{"description":"Reforms filtered by the caller's materias and jurisdiction.","content":{"application/json":{"schema":{"type":"object","properties":{"reforms":{"type":"array","items":{"allOf":[{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"rank":{"type":"string"},"status":{"type":"string"},"date":{"type":"string","format":"date"},"source_id":{"type":"string"},"headline":{"type":"string","nullable":true},"summary":{"type":"string","nullable":true},"reform_type":{"type":"string","nullable":true},"importance":{"type":"string","nullable":true},"materia_count":{"type":"integer"},"omnibus_topic_count":{"type":"integer"}}},{"type":"object","properties":{"match_ratio":{"type":"number"},"matched_topics":{"type":"array","items":{}}}}]}},"materias":{"type":"array","items":{"type":"string"}},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"400":{"description":"Missing/invalid materias, jurisdiction, limit, or offset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/changelog":{"get":{"parameters":[{"description":"Time window in weeks (default 4, max 12; larger values are clamped and reported via weeks_clamped)","schema":{"type":"string"},"in":"query","name":"weeks","required":false},{"description":"Jurisdiction code, e.g. es or es-ct","schema":{"type":"string"},"in":"query","name":"jurisdiccion","required":false},{"description":"Alias of jurisdiccion","schema":{"type":"string"},"in":"query","name":"jurisdiction","required":false},{"description":"Page size (default 50, max 100)","schema":{"type":"string"},"in":"query","name":"limit","required":false},{"description":"Rows to skip for pagination (default 0, max 10000). Use has_more to know if another page exists.","schema":{"type":"string"},"in":"query","name":"offset","required":false},{"description":"Not supported: returns 400. Use weeks to set the time window.","schema":{"type":"string"},"in":"query","name":"since","required":false}],"operationId":"getV1Changelog","summary":"Public changelog","description":"Returns recent reforms with AI summaries, newest first. Filterable by jurisdiction and time window (weeks), paginated with limit/offset. The response echoes the parameters actually applied (weeks, weeks_requested, weeks_clamped, limit, offset) and has_more.","tags":["Reformas"],"responses":{"200":{"description":"Recent reforms with AI summaries, newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"reforms":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"rank":{"type":"string"},"status":{"type":"string"},"date":{"type":"string","format":"date"},"source_id":{"type":"string"},"headline":{"type":"string","nullable":true},"summary":{"type":"string","nullable":true},"reform_type":{"type":"string","nullable":true},"importance":{"type":"string","nullable":true},"materia_count":{"type":"integer"},"omnibus_topic_count":{"type":"integer"}}}},"date_range":{"type":"string","example":"2026-08-28 to 2026-09-25"},"weeks":{"type":"integer","description":"Window actually applied (clamped to <= 12)."},"weeks_requested":{"type":"integer"},"weeks_clamped":{"type":"boolean"},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean"}}}}}},"400":{"description":"Invalid weeks/limit/offset, or the unsupported 'since' param was used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/reforms/{normId}/{date}":{"get":{"parameters":[{"schema":{"type":"string"},"in":"path","name":"normId","required":true},{"schema":{"type":"string"},"in":"path","name":"date","required":true}],"operationId":"getV1ReformsByNormIdByDate","summary":"Reform detail","description":"Returns full detail for a specific reform of a law, including affected blocks and navigation to adjacent reforms.","tags":["Reformas"],"responses":{"200":{"description":"Full detail for a specific reform of a law, with navigation to adjacent reforms.","content":{"application/json":{"schema":{"type":"object","properties":{"law":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"short_title":{"type":"string"},"rank":{"type":"string"},"status":{"type":"string"},"source_url":{"type":"string","format":"uri"},"last_reform_date":{"type":"string","format":"date","nullable":true}}},"reform":{"type":"object","properties":{"norm_id":{"type":"string"},"date":{"type":"string","format":"date"},"source_id":{"type":"string"},"affected_blocks":{"type":"array","items":{"type":"string"}}}},"affected_blocks":{"type":"array","items":{"type":"string"}},"prev_reform_date":{"type":"string","format":"date","nullable":true},"next_reform_date":{"type":"string","format":"date","nullable":true},"source_url":{"type":"string","format":"uri"}}}}}},"404":{"description":"No reform of this law on this date.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/status":{"get":{"operationId":"getV1Status","summary":"Corpus freshness status","description":"Reports how fresh the ingested corpus is: total norms/reforms, the most recent reform date actually in the database (excluding implausible/corrupt dates), and days elapsed since it. Exists because Ley Abierta only ingests BOE-consolidated legislation, which lags the daily bulletin by 1-2 weeks — this endpoint lets clients communicate that lag instead of it reading as a stalled or broken site. `last_sync` is best-effort (see `last_sync_source`): there is currently no fully reliable 'pipeline last ran' heartbeat for nights where the BOE published nothing new.","tags":["Sistema"],"responses":{"200":{"description":"Corpus freshness.","content":{"application/json":{"schema":{"type":"object","properties":{"norms_count":{"type":"integer"},"reforms_count":{"type":"integer"},"corpus_max_published_at":{"type":"string","format":"date"},"last_reform_date":{"type":"string","format":"date","nullable":true},"days_since_last_reform":{"type":"integer","nullable":true},"last_sync":{"type":"string","nullable":true},"last_sync_source":{"type":"string","nullable":true}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/omnibus":{"get":{"parameters":[{"schema":{"type":"string"},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"query","name":"since","required":false}],"operationId":"getV1Omnibus","summary":"List omnibus laws","description":"Returns recent omnibus laws (laws bundling multiple unrelated topics) with topic counts.","tags":["Ómnibus"],"responses":{"200":{"description":"Recent omnibus laws with topic counts.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"rank":{"type":"string"},"materia_count":{"type":"integer"},"topic_count":{"type":"integer"},"sneaked_count":{"type":"integer"},"latest_reform_date":{"type":"string","format":"date"}}}}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/omnibus/{normId}":{"get":{"parameters":[{"schema":{"type":"string"},"in":"path","name":"normId","required":true}],"operationId":"getV1OmnibusByNormId","summary":"Omnibus law detail","description":"Returns full detail for an omnibus law including per-topic AI breakdowns and sneaked topic count.","tags":["Ómnibus"],"responses":{"200":{"description":"Omnibus law detail with per-topic AI breakdowns.","content":{"application/json":{"schema":{"type":"object","properties":{"topics":{"type":"array","items":{"type":"object"}},"sneaked_count":{"type":"integer"}},"additionalProperties":true}}}},"404":{"description":"No norm with this id, or it is not an omnibus law.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/ask":{"post":{"parameters":[],"operationId":"postV1Ask","summary":"Ask a question about Spanish legislation","description":"Send a question in plain language and receive an answer grounded in real legislative articles with verifiable citations.","tags":["Preguntas"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string"},"jurisdiction":{"type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string"},"jurisdiction":{"type":"string"}}}},"text/plain":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string"},"jurisdiction":{"type":"string"}}}}}},"responses":{"200":{"description":"Cited, plain-language answer grounded in the retrieved articles.","content":{"application/json":{"schema":{"type":"object","properties":{"answer":{"type":"string"},"citations":{"type":"array","items":{"type":"object","properties":{"normId":{"type":"string"},"normTitle":{"type":"string"},"articleTitle":{"type":"string"},"anchor":{"type":"string"},"blockId":{"type":"string"},"citizenSummary":{"type":"string"},"verified":{"type":"boolean"}}}},"declined":{"type":"boolean"},"tldr":{"type":"string"},"nextQuestions":{"type":"array","items":{"type":"string"}},"suggestedQuestions":{"type":"array","items":{"type":"string"}},"meta":{"type":"object","properties":{"articlesRetrieved":{"type":"integer"},"temporalEnriched":{"type":"boolean"},"latencyMs":{"type":"integer"},"model":{"type":"string"}}}}}}}},"400":{"description":"Question missing, too short (<3 chars), or too long (>1000 chars).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request-rate limit (20/min per IP) or the question quota exceeded (2/min, 10/day per IP, 200/day global).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"reason":{"type":"string","enum":["per_minute","per_day","global_day"]},"retryAfterSeconds":{"type":"integer"},"remainingToday":{"type":"integer"},"limitPerDay":{"type":"integer"}}}}}},"503":{"description":"The RAG pipeline is unavailable (OPENROUTER_API_KEY not configured).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/ask/stream":{"post":{"parameters":[],"operationId":"postV1AskStream","summary":"Ask a question (streaming)","description":"Streaming variant — returns Server-Sent Events with text chunks followed by a final event with citations.","tags":["Preguntas"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string"},"jurisdiction":{"type":"string"}}}},"multipart/form-data":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string"},"jurisdiction":{"type":"string"}}}},"text/plain":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string"},"jurisdiction":{"type":"string"}}}}}},"responses":{"200":{"description":"Server-Sent Events: stage, quota, progress, chunk (repeated), then done — or error. See DEPLOY/CLAUDE.md for the event shapes; SSE bodies aren't representable as a single JSON schema.","content":{"text/event-stream":{"schema":{"type":"string"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"The RAG pipeline is unavailable (OPENROUTER_API_KEY not configured). Sent as an SSE 'error' event, not a plain JSON body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/health":{"get":{"operationId":"getHealth","summary":"Health check","description":"Returns API status, version, and total law count.","tags":["Sistema"],"responses":{"200":{"description":"API status, build version, and total law count.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"version":{"type":"string","description":"Git SHA of the running build, or 'dev'."},"laws":{"type":"integer"},"last_ingest":{"type":"string","format":"date-time","nullable":true}}}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/og/{id}":{"get":{"operationId":"getOgById","summary":"Get OG image for a law","tags":["Sistema"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"The pre-generated Open Graph share image for this law.","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Missing id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No OG image for this id (not yet generated, or the id doesn't exist).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"5XX":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message (Spanish or English, not machine-parseable)."},"code":{"type":"string","description":"Machine-readable error code. Only present on framework-level errors (unmatched route, validation, internal error) — see services/api-errors.ts."},"hint":{"type":"string","description":"Pointer to further documentation. Only present alongside `code`."}},"additionalProperties":true}}}}