API REST do Lexbase

A API REST do Lexbase dá acesso directo ao motor de pesquisa de legislação portuguesa consolidada — o mesmo que serve o MCP, sem camadas pelo meio. Cada resultado traz a citação exacta, o texto integral do artigo e o estado de vigência, incluindo quem revogou o quê e quando. Esta página documenta a autenticação, a pesquisa, a leitura de artigos e diplomas e os limites de utilização, com exemplos que pode copiar e correr.

Autenticação

Todos os endpoints exigem uma chave de API — excepto GET /health, que é público. A chave cria-se em lexbase.pt/mcp e começa por lx_. Envia-se no cabeçalho Authorization de todos os pedidos:

curl -s "https://api.lexbase.pt/themes" \
  -H "Authorization: Bearer lx_A_SUA_CHAVE"

A base da API é https://api.lexbase.pt.

Sem chave válida, a resposta é 401 com um corpo JSON que diz exactamente o que falhou:

{"error": "invalid_token", "error_description": "unknown or invalid API key"}

As descrições possíveis: no bearer token presented (não enviou o cabeçalho), malformed API key, unknown or invalid API key, API key expired e API key revoked. Um 401 não se resolve com retries — corrija a chave.

GET /health devolve o estado do serviço sem autenticação: índice carregado, dimensão do corpus e o tier de qualidade activo (rerank ou hybrid).

Pesquisar legislação

GET /search recebe uma pergunta em linguagem natural ou termos de pesquisa e devolve os fragmentos (artigos) mais relevantes do corpus:

curl -sG "https://api.lexbase.pt/search" \
  --data-urlencode "q=cessação do contrato de arrendamento" \
  -d "k=3" \
  -H "Authorization: Bearer lx_A_SUA_CHAVE"

Parâmetros:

ParâmetroPor omissãoDescrição
qobrigatórioA pergunta ou os termos de pesquisa.
k8Número de resultados.
tipoFiltra por tipo de diploma (o valor que vem em diploma.tipo nos resultados).
tema_tag_idFiltra por tema; os ids obtêm-se em GET /themes.
in_force_onlytrueSó lei vigente. Com false, inclui lei revogada — sempre marcada como tal.
modererankO modo de maior qualidade. Aceita também hybrid, dense, sparse e bm25.

A resposta é {"query": …, "total": N, "hits": […]}. Cada hit tem esta forma (encurtada):

{
  "fragment_id": "1138222475",
  "score": 0.97312,
  "citation": "«designação do diploma», Artigo …º «epígrafe»",
  "nome": "Artigo …º",
  "epigrafe": "…",
  "breadcrumb": ["…", "…"],
  "texto": "texto integral e consolidado do artigo…",
  "highlight_span": {"start": 132, "end": 508},
  "term_spans": [{"start": 140, "end": 149}],
  "diploma": {"diploma_id": "34509075", "designacao": "…", "tipo": "…",
               "emissor": "…", "eli": "…", "fonte_url": "…"},
  "in_force": {"data_entrada_vigor": "…", "vigente": true, "note": null},
  "amendments": [],
  "temas": []
}

Os campos que interessam:

  • `citation` — a citação pronta a usar: designação do diploma, artigo e epígrafe. É este o texto a citar num parecer ou numa peça.
  • `texto` — o texto integral consolidado do artigo, não um excerto.
  • `highlight_span` — o intervalo de caracteres dentro de texto que correspondeu à pesquisa. Serve para destacar o trecho relevante sem voltar a processar o texto.
  • `term_spans` — as posições dos termos da pesquisa dentro desse trecho (no máximo 50), para sublinhar termo a termo.
  • `breadcrumb` — o caminho estrutural até ao artigo (livro, título, capítulo, …).
  • `in_force` — o estado de vigência. Leia a secção seguinte antes de citar seja o que for.
  • `amendments` — as alterações registadas sobre o artigo.
  • `diploma.eli` e `diploma.fonte_url` — o identificador ELI e a ligação à fonte oficial.

Cada hit inclui ainda chunk_id, score_components e diploma.diploma_legis_id, úteis para depuração e cruzamento de dados.

Vigência: vigente e revogado_por

Citar lei revogada é o erro caro desta área, e o contrato da API existe para o tornar difícil. Toda a resposta que contém texto de lei — um hit de pesquisa, um /fragment, um /diploma?render=full — traz um objecto in_force:

  • `data_entrada_vigor` — quando a disposição entrou em vigor. Nos hits de pesquisa e no /fragment, quando vigora desde a versão original do diploma, note diz "desde a versão original" (os nós de /diploma?render=full não incluem o campo note).
  • `vigente`true ou false. É false se a disposição foi suspensa ou revogada, quer o diploma inteiro quer o artigo isolado.
  • Quando revogada, aparecem `revogado_por` (o diploma revogador) e `data_revogacao`:
"in_force": {
  "data_entrada_vigor": "…",
  "vigente": false,
  "revogado_por": "…",
  "data_revogacao": "…"
}

A pesquisa exclui lei revogada por omissão (in_force_only=true). A lei revogada não é apagada do corpus — para litigar factos de 2015 é precisa a lei de 2015 — por isso in_force_only=false devolve-a de propósito, sempre marcada. A regra prática: antes de citar, verifique in_force.vigente; se for false, revogado_por diz quem a matou e data_revogacao diz quando.

Ler um artigo ou um diploma

`GET /fragment/{fragment_id}` é a leitura canónica de um artigo — a partir do fragment_id de qualquer hit:

curl -s "https://api.lexbase.pt/fragment/1138222475" \
  -H "Authorization: Bearer lx_A_SUA_CHAVE"

Devolve fragment_id, diploma_id, citation, breadcrumb, nome, epigrafe, texto, in_force e temas. Com ?context=1 acrescenta context com parent, prev e next — o artigo anterior e o seguinte na estrutura do diploma.

`GET /fragment/{fragment_id}/amendments` devolve o histórico de alterações do artigo: {"target": {"fragment_id", "diploma_id", "nome"}, "amendments": […]}.

`GET /diploma/{diploma_id}` lê o diploma inteiro, com três níveis de detalhe via render:

  • render=meta (por omissão) — o cabeçalho: designação, sumário, emissor, ELI, datas de publicação e de consolidação, temas, número de fragmentos e de alterações.
  • render=tree — acrescenta nodes: a estrutura completa do diploma (fragment_id, nome, epigrafe, depth, has_amendments por nó), sem texto. É o modo certo para navegar.
  • render=full — como tree, mas cada nó traz também texto e in_force. Atenção: é o documento inteiro — o Código Civil ronda os 4 MB.

Com ?focus=<fragment_id> em render=tree ou render=full, a resposta inclui path_to_focus: o caminho de fragment_ids da raiz até esse artigo. Com o render=meta por omissão, o focus é ignorado.

curl -s "https://api.lexbase.pt/diploma/34509075?render=tree" \
  -H "Authorization: Bearer lx_A_SUA_CHAVE"

Um id desconhecido devolve 404 com {"detail": "diploma not found"} ou {"detail": "fragment not found"}.

`GET /themes` lista os temas do corpus — {"themes": [{"tag_id", "titulo", "act_count"}]} — e os tag_id servem de filtro tema_tag_id na pesquisa.

total: 0 significa «não temos»

Quando a pesquisa não encontra apoio no corpus, a resposta é HTTP 200 com total: 0, hits: [] e um campo note a explicar porquê. Não é um erro — é a resposta correcta:

{"query": "…", "total": 0, "hits": [], "note": "no matching legislation found"}

Há dois casos principais:

  • `no matching legislation found` — nada no corpus sustenta a pergunta.
  • `out of scope: …` — a pergunta é sobre lei estrangeira. O corpus cobre exclusivamente legislação portuguesa, e uma pergunta sobre «lei brasileira» é recusada antes da pesquisa: um artigo português topicamente parecido seria a jurisdição errada, por muito bem que ficasse classificado.

(Um q vazio devolve igualmente total: 0, com note: "empty query".)

O que fazer com total: 0: tratá-lo como informação. Não repita a pesquisa com termos cada vez mais vagos à espera de um resultado qualquer e, se está a alimentar um LLM, não deixe o modelo preencher o vazio com memória. A abstenção honesta é preferível a uma citação inventada — é para isso que ela existe.

Limites de utilização

Cada pedido conta como uma pesquisa do seu saldo: pesquisar (GET /search), ler um artigo (GET /fragment) ou um diploma (GET /diploma, em qualquer render), o histórico de alterações e a lista de temas contam todos igual. Só GET /health não conta.

A conta Lexbase gratuita inclui:

LimiteConta gratuita
Pesquisas por mês200
Pedidos por minuto20
Chaves2

As pesquisas no navegador e os pedidos por chave saem do mesmo saldo mensal de 200 pesquisas.

Ao exceder um limite, a resposta é 429 com o cabeçalho Retry-After:

  • Ritmo (por minuto)429 com Retry-After: 60. Espere e retome.
  • Quota mensal429 com error: "quota_exceeded"; os números estão na descrição:
{
  "error": "quota_exceeded",
  "error_description": "monthly quota exceeded (200/200 pesquisas)"
}

O Retry-After da quota aponta para a viragem do mês (00:00 UTC do dia 1). Um pedido recusado com 429 não consome pesquisas — a recusa devolve o que tinha debitado e conta apenas como pedido nas estatísticas. Retries em ciclo não inflacionam o saldo, mas também não servem ninguém: respeite o Retry-After.