Primeiros passos
Todas as chamadas partem da mesma base e devolvem JSON. Os endpoints públicos não exigem autenticação; os privados exigem a sua chave de acesso.
Endereço base
/pt-pt/api O prefixo de idioma faz parte da URL (pt-pt), mas não altera os dados retornados — os registros vêm do banco exatamente como foram cadastrados em campo.
curl -G -d "municipality=Ponta Pora" --data-urlencode "country=Brasil" \
/pt-pt/api/lastcountingpublic Se a resposta for uma lista JSON, a integração já está funcionando.
key é lida da query string (?key=SUA_CHAVE). Enviá-la apenas no corpo do formulário devolve 404 "Wrong key". Os demais campos continuam indo no corpo da requisição. Consultar dados públicos
O endpoint /api/lastcountingpublic devolve as últimas contagens de ovos com latitude, longitude, semana e ano epidemiológicos. É o ponto de partida para mapas, painéis e estudos.
/api/lastcountingpublicPúblicocurl -G \
--data-urlencode "municipality=Ponta Pora" \
--data-urlencode "date_start=2025-01-01" \
--data-urlencode "date_end=2025-12-31" \
/pt-pt/api/lastcountingpublic import requests
BASE = "/pt-pt/api"
resposta = requests.get(
BASE + "/lastcountingpublic",
params={
"municipality": "Ponta Pora",
"date_start": "2025-01-01",
"date_end": "2025-12-31",
},
timeout=60,
)
resposta.raise_for_status()
for contagem in resposta.json():
print(contagem["date"], contagem["ovitrap_id"], contagem["eggs"]) const BASE = "/pt-pt/api";
const params = new URLSearchParams({
state: "MS",
date_start: "2025-01-01",
});
const resposta = await fetch(BASE + "/lastcountingpublic?" + params);
const contagens = await resposta.json();
console.log(contagens.length, "contagens"); Os mesmos parâmetros de localização (country, state, municipality) valem para os demais endpoints públicos: ovitrampas, quarteirões, EDLs, manutenções de EDLs e pontos estratégicos. Trocar apenas o caminho do endpoint já muda o conjunto de dados.
Percorrer todas as páginas
Os endpoints paginados aceitam page de 1 a 100. Acima disso a API responde com o texto "Maximum pagination is 100" e status 200 — ou seja, o corpo deixa de ser uma lista. Sempre verifique o tipo antes de iterar.
import requests
BASE = "/pt-pt/api"
def baixar_tudo(endpoint, **filtros):
"""Percorre as paginas ate a API devolver uma pagina vazia."""
registros = []
for pagina in range(1, 101):
resposta = requests.get(
BASE + endpoint,
params=dict(filtros, page=pagina),
timeout=60,
)
resposta.raise_for_status()
dados = resposta.json()
# limite de paginacao atingido: a API devolve uma string, nao uma lista
if not isinstance(dados, list):
print("Aviso:", dados)
break
if not dados:
break
registros.extend(dados)
return registros
ovitrampas = baixar_tudo("/getmunicipalityovitrapspublic", municipality="Ponta Pora")
print(len(ovitrampas), "ovitrampas") date_start e date_end para dividir o período em intervalos menores. Sincronização incremental
Para manter uma base espelhada, não baixe tudo de novo a cada execução. O endpoint /api/lastcountingpublic aceita id, que devolve apenas as contagens a partir daquele identificador, e também date (data de inclusão) e date_collect (data de coleta).
import json
import os
import requests
BASE = "/pt-pt/api"
ESTADO = "ultimo_id.json"
def ultimo_id_lido():
if os.path.exists(ESTADO):
with open(ESTADO) as arquivo:
return json.load(arquivo)["counting_id"]
return 0
def sincronizar():
ultimo = ultimo_id_lido()
novas = []
for pagina in range(1, 101):
resposta = requests.get(
BASE + "/lastcountingpublic",
params={"municipality": "Ponta Pora", "id": ultimo, "page": pagina},
timeout=60,
)
resposta.raise_for_status()
dados = resposta.json()
if not isinstance(dados, list) or not dados:
break
novas.extend(dados)
if novas:
maior = max(item["counting_id"] for item in novas)
with open(ESTADO, "w") as arquivo:
json.dump({"counting_id": maior}, arquivo)
return novas
print(len(sincronizar()), "contagens novas") Guarde sempre o maior counting_id recebido, e não a data da execução: uma contagem lançada em campo hoje pode se referir a uma semana anterior, e o filtro por id não deixa esses registros escaparem.
Exportar para CSV
As contagens públicas já vêm com coordenadas, então dá para gerar uma planilha ou alimentar um mapa sem nenhuma junção adicional.
import csv
import requests
BASE = "/pt-pt/api"
COLUNAS = [
"counting_id",
"municipality",
"state_code",
"ovitrap_id",
"latitude",
"longitude",
"week",
"year",
"eggs",
"date",
"date_collect",
]
resposta = requests.get(
BASE + "/lastcountingpublic",
params={"state": "MS", "date_start": "2025-01-01"},
timeout=60,
)
contagens = resposta.json()
with open("contagens.csv", "w", newline="", encoding="utf-8") as arquivo:
escritor = csv.DictWriter(arquivo, fieldnames=COLUNAS, extrasaction="ignore")
escritor.writeheader()
escritor.writerows(contagens)
print("Arquivo contagens.csv gerado com", len(contagens), "linhas") Enviando dados
Os exemplos a seguir usam a API privada e exigem uma chave. O escopo geográfico da chave define o que ela pode gravar — uma chave municipal só grava no próprio município.
/api/postcountingPrivadoEnviar a leitura de uma ovitrampa existente
Envia a contagem de ovos de uma ovitrampa já cadastrada. A ovitrampa é localizada pelo ovitrap_group_id dentro do município da chave, e a semana epidemiológica é calculada a partir do campo date.
curl -X POST \
-d "ovitrap_group_id=97" \
-d "ovitrap_lat=-7.000000" \
-d "ovitrap_lng=-8.000000" \
-d "date=2025-01-20" \
-d "counting_observation_id=1" \
-d "counting_eggs=5" \
"/pt-pt/api/postcounting?key=SUA_CHAVE" import requests
BASE = "/pt-pt/api"
CHAVE = "SUA_CHAVE"
resposta = requests.post(
BASE + "/postcounting",
params={"key": CHAVE}, # a chave vai na URL
data={ # os dados vao no corpo
"ovitrap_group_id": 97,
"ovitrap_lat": -7.000000,
"ovitrap_lng": -8.000000,
"date": "2025-01-20",
"counting_observation_id": 1,
"counting_eggs": 5,
},
timeout=60,
)
print(resposta.status_code, resposta.json())
# 200 "Contagem registrada" /api/postcountingPrivadoInstalar uma ovitrampa nova junto da leitura
Se o ovitrap_group_id ainda não existir no município, a ovitrampa é criada na mesma requisição. Envie também o endereço e, se for o caso, o tipo (ovitrap_type_id: 1 urbana, padrão; 2 rural).
import requests
BASE = "/pt-pt/api"
CHAVE = "SUA_CHAVE"
resposta = requests.post(
BASE + "/postcounting",
params={"key": CHAVE},
data={
"ovitrap_group_id": 96,
"ovitrap_address_district": "Centro",
"ovitrap_address_street": "Rua das Flores",
"ovitrap_address_number": "123",
"ovitrap_address_complement": "",
"ovitrap_address_sector": "Setor 04",
"ovitrap_responsable": "Maria Souza",
"ovitrap_block_id": "12",
"ovitrap_type_id": 2, # 1 = urbana (padrao), 2 = rural
"ovitrap_lat": -7.000000,
"ovitrap_lng": -8.000000,
"date": "2025-01-20",
"counting_date_collect": "2025-01-27",
"counting_observation_id": 1,
"counting_eggs": 5,
},
timeout=60,
)
print(resposta.status_code, resposta.json()) 400— faltaovitrap_lat,ovitrap_lngouovitrap_group_id.400—ovitrap_type_iddiferente de 1 ou 2.404— já existe contagem para essa ovitrampa, ano e semana.500— a data enviada cai em uma semana epidemiológica futura ou inválida.
/api/postactionPrivadoRegistrar uma visita em quarteirão
Use block_id para lançar a visita em um quarteirão já cadastrado. Se o quarteirão ainda não existir, envie block_group_id com os dados do quarteirão e ele é criado automaticamente dentro do município da chave.
import requests
BASE = "/pt-pt/api"
CHAVE = "SUA_CHAVE"
visita = {
"block_id": 97,
"date": "2025-01-20",
"block_land_residence": 10,
"action_land_residence_out": 2,
"action_land_residence_breedings": 1,
"action_land_residence_treated": 1,
"action_deposit_a1_quantity": 4,
"action_deposit_a1_eliminated": 2,
"action_deposit_a1_treated": 1,
"action_deposit_a1_larvicid": 10,
"action_observation": "Foco encontrado em pneus nos fundos do imovel.",
}
resposta = requests.post(
BASE + "/postaction", params={"key": CHAVE}, data=visita, timeout=60
)
if resposta.status_code == 409:
print("Ja existe visita para esse quarteirao nessa semana")
else:
print(resposta.status_code, resposta.json()) visita = {
"block_group_id": 97,
"date": "2025-01-20",
"block_address_district": "Centro Historico",
"block_address_sector": "Setor 04 - Norte",
"block_coordinates": "[[-7.123, -34.845], [-7.124, -34.846]]",
"block_lat": -7.123456,
"block_lng": -34.845678,
"block_responsable": "Joao da Silva",
"block_land_residence": 10,
"action_land_residence_out": 2,
"action_land_residence_breedings": 1,
"action_land_residence_treated": 1,
"action_observation": "Area com alta densidade de recipientes descartaveis.",
}
resposta = requests.post(
BASE + "/postaction", params={"key": CHAVE}, data=visita, timeout=60
)
print(resposta.status_code, resposta.json()) A lista completa de campos de imóveis e de depósitos (a1, a2, b, c, d1, d2, e) está na referência do endpoint. Campos numéricos não enviados assumem zero, mas qualquer valor que não seja um número devolve 400.
Remover registros
As remoções são definitivas e sempre restritas ao município da chave.
# leitura de uma ovitrampa (identificada pela ovitrampa + data)
curl -X POST -d "ovitrap_group_id=97" -d "date=2025-01-20" \
"/pt-pt/api/postdeletecounting?key=SUA_CHAVE"
# a ovitrampa inteira
curl -X POST -d "ovitrap_group_id=97" \
"/pt-pt/api/postdeleteovitrap?key=SUA_CHAVE"
# visita de um quarteirao (quarteirao + data)
curl -X POST -d "block_group_id=97" -d "date=2025-01-20" \
"/pt-pt/api/postdeleteaction?key=SUA_CHAVE"
# o quarteirao inteiro
curl -X POST -d "block_group_id=97" \
"/pt-pt/api/postdeleteblock?key=SUA_CHAVE" /api/postdeletecounting e envie-a novamente com /api/postcounting — reenviar por cima devolve erro de duplicidade.Tratamento de erros
O corpo da resposta é sempre uma string JSON com a mensagem. Vale tratar caso a caso, porque nem todo erro merece nova tentativa:
| Situação | Código | O que fazer |
|---|---|---|
"Wrong key" | 404 | Confira se a chave está na query string e não no corpo. |
| Contagem duplicada | 404 | Já existe leitura para a ovitrampa nessa semana; remova antes de reenviar. |
| Visita duplicada | 409 | Já existe visita para o quarteirão nessa semana. |
| Fora do escopo | 403 | O quarteirão não pertence ao município da chave. Não repita. |
| Campo obrigatório ausente | 400 | Corrija o envio; repetir devolve o mesmo erro. |
| Semana ou ano inválidos | 500 | A data cai fora da semana epidemiológica aceita. Corrija o campo date. |
import time
import requests
NAO_REPETIR = {400, 403, 404, 409}
def enviar(url, chave, dados, tentativas=3):
for tentativa in range(tentativas):
try:
resposta = requests.post(
url, params={"key": chave}, data=dados, timeout=60
)
except requests.RequestException as erro:
print("Falha de rede:", erro)
time.sleep(5 * (tentativa + 1))
continue
if resposta.status_code in NAO_REPETIR:
print("Erro definitivo:", resposta.status_code, resposta.json())
return None
if resposta.ok:
return resposta.json()
# 500 e demais erros de servidor: espera progressiva e tenta de novo
time.sleep(5 * (tentativa + 1))
return None Boas práticas
- Filtre por município ou estado sempre que possível — sem filtro a consulta varre o país inteiro.
- Prefira sincronização incremental por
ida rebaixar toda a base todos os dias. - Envie as requisições em série. A API não é feita para dezenas de chamadas simultâneas por cliente.
- Verifique se a resposta é uma lista antes de iterar: as mensagens de limite de paginação vêm com status 200.
- Guarde o
counting_ide oblock_idrecebidos — são eles que permitem casar os registros do Conta Ovos com os do seu sistema. - Trate a semana epidemiológica como o campo determinante: o Conta Ovos aceita apenas uma leitura por ovitrampa por semana, e uma visita por quarteirão por semana.
- Acompanhe a página de mudanças antes de atualizar a sua integração.