Começando
- Crie uma conta de empresa (CNPJ) no 10leva.
- Recarregue o saldo na aba Saldo (Pix ou cartão).
- Na aba API, crie uma chave. Ela começa com
lv_e aparece uma vez só. - Envie a chave em todas as chamadas:
Authorization: Bearer lv_...
Chame a API do seu servidor. A chave dá acesso ao seu saldo: nunca coloque no navegador, no código do site ou no app instalado no celular do cliente.
Endereço base: https://10oferta.com/api/leva/v1 (ou o domínio onde o seu 10leva estiver).
Rotas
| GET | /saldo | Saldo pré-pago da empresa, em centavos. |
| GET | /tarifas | Tarifas em vigor (base, por km, mínimo, limite de distância). |
| POST | /cotacoes | Calcula o valor. A cotação vale 30 minutos. |
| POST | /entregas | Cria a entrega (com cotacao_id ou com os endereços) e desconta do saldo. |
| GET | /entregas | Lista as últimas entregas (?limite=1..100, ?referencia_externa=...). |
| GET | /entregas/{id} | Status, motoboy, código de entrega e link de acompanhamento. |
| POST | /entregas/{id}/cancelar | Cancela antes da coleta. O valor volta para o saldo. |
Pedir uma entrega
tipo é "entrega" ou "passageiro". Na coleta, nome e telefone são obrigatórios; na entrega, também (exceto em corrida com passageiro). Use referencia_externa com o número do pedido no seu sistema: repetir a chamada com a mesma referência devolve a mesma entrega, sem cobrar de novo.
curl -X POST https://10oferta.com/api/leva/v1/entregas \
-H "Authorization: Bearer lv_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"tipo": "entrega",
"coleta": {
"cep": "80010-000", "rua": "Rua XV de Novembro", "numero": "100",
"bairro": "Centro", "cidade": "Curitiba", "uf": "PR",
"nome": "Loja Exemplo", "telefone": "41999990000",
"instrucoes": "Retirar no balcão"
},
"entrega": {
"cep": "80250-000", "rua": "Av. Sete de Setembro", "numero": "2500",
"complemento": "Apto 31", "bairro": "Rebouças", "cidade": "Curitiba", "uf": "PR",
"nome": "Maria Souza", "telefone": "41988887777"
},
"descricao": "Caixa pequena, 2 kg",
"observacoes": "Frágil",
"referencia_externa": "pedido-1234"
}'Resposta (201):
{
"id": "6b0c2a8e-...",
"tipo": "entrega",
"status": "procurando_motoboy",
"valor_centavos": 1490,
"distancia_km": 3.9,
"codigo_entrega": "LV-7K2QX",
"referencia_externa": "pedido-1234",
"acompanhar_url": "https://10oferta.com/10leva/acompanhar/Xy...",
"coleta": { "endereco": "...", "nome": "Loja Exemplo", "telefone": "..." },
"entrega": { "endereco": "...", "nome": "Maria Souza", "telefone": "..." },
"motoboy": null,
"criada_em": "2026-10-27T14:03:11Z", ...
}Mande ao seu cliente o acompanhar_url e o codigo_entrega: o motoboy só conclui a entrega com esse código (ou com uma foto de comprovação).
Cotar antes de pedir
Mostre o frete no seu checkout com uma cotação e depois peça com o cotacao_id: o valor cobrado é exatamente o da cotação.
POST https://10oferta.com/api/leva/v1/cotacoes { "tipo": "entrega", "coleta": {...}, "entrega": {...} }
→ 201 { "id": "c1d2...", "valor_centavos": 1490, "distancia_km": 3.9, "expira_em": "..." }
POST https://10oferta.com/api/leva/v1/entregas { "cotacao_id": "c1d2...", "descricao": "...", "referencia_externa": "pedido-1234" }Status
procurando_motoboyPaga e na fila dos motoboys perto da coleta.motoboy_a_caminhoUm motoboy aceitou e está indo até a coleta.em_rotaO motoboy pegou o item (ou o passageiro).entregueConcluída com o código de entrega (ou foto).canceladaCancelada. Se estava paga, o valor voltou para o saldo.
Webhook
Cadastre um endereço https:// na aba API. A cada mudança enviamos um POST com o evento e a entrega no mesmo formato da API. Eventos: entrega.procurando_motoboy, entrega.motoboy_a_caminho, entrega.em_rota, entrega.entregue, entrega.cancelada.
Confira a assinatura no cabeçalho X-10leva-Assinatura: t=TIMESTAMP,v1=HMAC, em que HMAC é o SHA-256 de `${t}.${corpo}` com o seu segredo whlv_.... Recuse avisos com mais de 5 minutos.
// Node.js
import crypto from "node:crypto";
function assinaturaValida(corpo, cabecalho, segredo) {
const partes = Object.fromEntries(cabecalho.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(partes.t)) > 300) return false;
const esperado = crypto.createHmac("sha256", segredo).update(`${partes.t}.${corpo}`).digest("hex");
const recebido = Buffer.from(partes.v1 ?? "");
return recebido.length === esperado.length && crypto.timingSafeEqual(Buffer.from(esperado), recebido);
}Erros e limites
{ "erro": { "codigo": "saldo_insuficiente", "mensagem": "Saldo insuficiente. Recarregue..." } }400 dados_invalidos(comdetalhespor campo),401 nao_autorizado,402 saldo_insuficiente,404 nao_encontrada.409 cotacao_invalida(vencida ou já usada),409 nao_cancelavel(o motoboy já coletou).422 coleta_nao_encontrada/entrega_nao_encontrada(endereço não achado no mapa),422 distancia_maxima.429 limite: 120 chamadas por minuto por chave; 30 cotações e 30 pedidos por minuto.
