Documentação

O pncp.dev repassa as rotas públicas do PNCP. Se você já sabe chamar o pncp.gov.br, já sabe usar isto: troque o endereço e mande a sua chave num header.

Começar

Crie a sua chave no painel. Leva um minuto e não pede cartão. Depois é só chamar:

terminal
curl "https://api.pncp.dev/api/consulta/v1/contratacoes/publicacao?dataInicial=20261022&dataFinal=20261028&codigoModalidadeContratacao=6&pagina=1&tamanhoPagina=10" \
  -H "x-api-key: $PNCP_API_KEY"

A resposta é o JSON do PNCP, sem nenhuma alteração.

Sua chave

Toda chamada precisa da sua chave. O melhor jeito de mandar é no header x-api-key:

header
x-api-key: pncp_a1b2c3d4e5f6...

Para um teste rápido, a chave também pode ir na URL, no parâmetro api_key. Serve para uma aba do navegador ou para uma ferramenta que não deixa mexer em header:

na URL, para testar
curl "https://api.pncp.dev/api/consulta/v1/orgaos?pagina=1&api_key=$PNCP_API_KEY"

A gente tira esse parâmetro da URL antes de tudo: ele nunca chega no PNCP e não entra no cache. Ainda assim, no seu código prefira o header. URL fica salva no histórico do navegador e costuma aparecer em log de erro.

A chave inteira aparece uma vez só, na hora que você cria. Depois o painel mostra só o começo dela. Perdeu? Crie outra e revogue a antiga. Você pode ter várias ao mesmo tempo, uma para cada ambiente.

Rotas

Qualquer rota que comece com /api/ no PNCP funciona aqui, com os mesmos parâmetros. As mais usadas:

/api/consulta/v1/contratacoes/publicacao
O que devolve
licitações publicadas num período
/api/consulta/v1/contratacoes/proposta
O que devolve
licitações com proposta ainda aberta
/api/consulta/v1/contratos
O que devolve
contratos assinados
/api/consulta/v1/atas
O que devolve
atas de registro de preço
/api/consulta/v1/orgaos/{cnpj}/compras/{ano}/{seq}
O que devolve
detalhe de uma compra

Os parâmetros são os mesmos do PNCP: data em AAAAMMDD, tamanhoPagina de 10 para cima. Para saber o que cada campo significa, vale a documentação oficial do PNCP.

Cota e limites

Cada chamada desconta uma requisição da sua cota do mês. A cota zera todo dia 1º, no horário de Brasília. O plano grátis dá 100.000 requisições por mês.

Quando a cota acaba, seus créditos entram no lugar sozinhos e nada para. Sem cota e sem crédito, a resposta é 429, com a data em que a cota volta.

Também existe um limite por minuto, contado por chave, para não sobrecarregar o PNCP. Ele só conta as chamadas que chegam lá: resposta que veio do cache não gasta esse limite.

Chamada recusada também desconta. Passou do limite por minuto, acabou a cota, o PNCP falhou: cada tentativa conta uma requisição. É isso que segura um loop que saiu do controle antes de ele pesar no PNCP.

Cache

A gente guarda cada resposta por 5 minutos. Nesse tempo, quem pedir a mesma coisa recebe na hora, sem ir até o PNCP. Ainda assim desconta da sua cota: a chamada foi sua.

O que vem na resposta

Toda resposta que deu certo traz o saldo da sua cota nos headers, para você acompanhar sem abrir o painel:

resposta
x-quota-limit: 100000
x-quota-remaining: 99994
x-quota-reset: 1793502000
x-pncp-dev: hit

O x-pncp-dev diz o que aconteceu com a sua chamada:

  • hitbuscamos no PNCP agora
  • cacheveio da cópia que a gente guardou
  • authfaltou a chave, ou ela não vale mais
  • quotaacabou a cota do mês ou o limite por minuto
  • deniedessa rota não é pública
  • methodsó GET passa por aqui
  • waf-blockedo PNCP recusou, mesmo depois de tentarmos de novo
  • upstream-erroro PNCP não respondeu, ou a falha foi nossa

Erros

Os erros que são nossos vêm em JSON. O code nunca muda, então dá para o seu programa decidir o que fazer; a mensagem em português é para você ler no log:

429
{
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Cota mensal esgotada. Compre um pacote ou aguarde o próximo mês.",
    "resetAt": 1793502000
  }
}

Erro que vem do próprio PNCP (uma data errada, por exemplo) chega até você do jeito que ele mandou, com o status original. A gente não mexe na resposta do PNCP.

Status e filas

O status é público, sem chave e sem desconto na cota. A mesma medição aparece em /status e no JSON:

terminal
curl "https://api.pncp.dev/_/status"

Escolha pelo caminho da chamada, não pelo nome da sua fila. Uma tarefa chamada "detail" pode usar /api/consulta/v1/:

pncp-dev
O que é testado
Roteador da API e leitura do banco do pncp.dev. Teste interno, sem DNS nem conexão externa.
pncp-site
O que é testado
Página inicial do pncp.dev pela rede. Sua falha não pausa chamadas à API.
pncp-search
O que é testado
/api/search/: uma busca com uma resposta por página.
pncp-consulta
O que é testado
/api/consulta/v1/: os detalhes de uma compra publicada.
pncp-items
O que é testado
/api/pncp/v1/: os itens de uma compra publicada.

O JSON tem schemaVersion: 1, now,pollAfterSeconds: 60 e uma lista components. Os horários são segundos desde 1970, no padrão UNIX. Cada componente traz:

  • status: operational, down ou unknown (sem medição válida).
  • checkedAt: início do último teste. expiresAt: esse horário mais 180 segundos. Sem teste, ambos são null.
  • ready: só é true se o serviço e a API com o banco estiverem respondendo com medições válidas.
  • readyUntil: até quando esse sinal vale. É o primeiro vencimento entre o teste do serviço e o da API com o banco. Vale null quando não está pronto.
  • httpStatus, latencyMs e error: última resposta, tempo em milissegundos e motivo da falha. Sem resposta HTTP, httpStatus é null.

Uma leitura do status retorna HTTP 200 mesmo quando um serviço está fora do ar. O status geral pode ser degraded quando só parte falha. Para decidir sobre uma tarefa, use o componente dela, não o HTTP 200 nem o status geral.

JavaScript: pode iniciar uma tarefa de detalhes?
async function canStart(service) {
  try {
    const response = await fetch("https://api.pncp.dev/_/status", {
      signal: AbortSignal.timeout(5000),
      cache: "no-store"
    });
    if (!response.ok) return false;
    const status = await response.json();
    if (status.schemaVersion !== 1 || !Array.isArray(status.components)) return false;
    const component = status.components.find(c => c.id === service);
    return component?.ready === true
      && Number.isFinite(component.readyUntil)
      && Date.now() / 1000 < component.readyUntil;
  } catch {
    return false;
  }
}

console.log(await canStart("pncp-consulta")); // true ou false

Consulte a cada 60 segundos e compartilhe o resultado entre os consumidores da fila, em vez de consultar por tarefa. Mesmo com um resultado salvo, confirareadyUntil antes de iniciar cada tarefa.

Adiar não é falhar

Se vier ready: false, se o prazo vencer ou se o status não responder, adie só as tarefas desse serviço, sem gastar uma tentativa. Confira de novo em um minuto. Retome quando vier uma medição nova e válida. Nunca descarte a tarefa.

O teste usa o mesmo acesso ao PNCP das chamadas reais, com nova tentativa e caminho alternativo quando cabem, mas sem respostas guardadas. Exigimos sucesso HTTP e JSON com os campos esperados. Um 403, 429, 503 ou uma página de bloqueio não conta como disponível. Testar uma rota não garante que todas as outras estão funcionando.

O status não substitui o tratamento de erro de cada chamada. Uma falha pode começar entre dois testes. Se a API ou o banco cair, o status também pode ficar inacessível: nesse caso, pause as tarefas até conseguir confirmar de novo.

Agentes de IA (MCP)

Conecte seu app de IA para consultar licitações, contratos e atas do PNCP ou conferir a cota da sua conta. O acesso é somente leitura.

Ver como conectar seu app de IA

O que não dá para fazer

  • Só GET. Aqui só se consulta dado público. POST, PUT e DELETE recebem 405.
  • Só rotas /api/. O resto do site do PNCP não passa por aqui.
  • Nada de área logada do gov.br. A gente nunca repassa o header Authorization. Por isso a API de envio de dados do PNCP não funciona aqui, e isso é de propósito.