Comece em 5 minutos
Obtenha uma API key, crie envios e rotas, gerencie a frota (veículos, motoristas, atribuições) e assine webhooks. A API Reference documenta as 41 rotas HTTP (68 operações) do contrato atual.
Obtenha sua API key
Peça ao administrador para acessar o dashboard, seção Integrações → API Keys, e gerar uma key com pelo menos:
shipments:readshipments:writeshipments:deleteroutes:readroutes:writeroutes:deleteroutes:optimizevehicles:readvehicles:writevehicles:deletedrivers:readdrivers:writedrivers:deleteassignments:readassignments:writeassignments:deleteevents:readwebhooks:manage
A key é exibida UMA única vez ao criá-la. Guarde-a no seu gerenciador de segredos. Formato: mss_live_* para produção ou mss_test_* para sandbox.
export MASSIMPLE_BASE=https://api.massimple.la/v1, export MASSIMPLE_KEY=mss_test_xxx.
Valide que a key funciona
O endpoint /v1/whoami retorna a aplicação e a empresa à qual sua key aponta. Sanity check antes de qualquer outra coisa.
curl $MASSIMPLE_BASE/whoami \
-H "Authorization: Bearer $MASSIMPLE_KEY"
Espera-se 200 com:
{
"application": {
"id": "...",
"name": "Mi integración",
"env": "test",
"scopes": ["shipments:write", "events:read", "webhooks:manage"]
},
"company_id": "..."
}
Crie seu primeiro envio
POST em /v1/shipments. Não envie companyId nem status: o envio sempre é criado como pending. Se sua empresa tem um único centro de distribuição, pode omitir origin_distribution_center_id.
Campos principais (detalhe completo em API Reference → Criar envio):
| Campo | Obrigatório | Notas |
|---|---|---|
destination | Sim | Objeto destino |
destination.address | Sim | Endereço; geocodificado se não houver lat/lng |
destination.latitude / longitude | Não* | *Recomendados para roteirização |
content | Sim | Descrição do envio |
origin_distribution_center_id | Não | Obrigatório se tiver mais de um CD |
external_id, amount_to_collect, scheduled_date, metadata | Não | Opcionais |
curl -X POST $MASSIMPLE_BASE/shipments \
-H "Authorization: Bearer $MASSIMPLE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"external_id": "ORD-1001",
"destination": {
"name": "Juan Pérez",
"address": "Av. Corrientes 1234, CABA",
"latitude": -34.6037,
"longitude": -58.3816,
"contact_phone": "+5491155557777"
},
"content": "Caja con dos productos",
"amount_to_collect": 0,
"scheduled_date": "2026-05-20"
}'
Resposta 201 com formato público: id (shp_...), tracking_number, external_id, etc.
Assine eventos com um webhook
Registre uma URL HTTPS pública no dashboard (Integrações → Webhooks) ou via API. Sempre que algo muda (status de envio, de rota, etc.) enviamos um POST assinado.
curl -X POST $MASSIMPLE_BASE/webhooks \
-H "Authorization: Bearer $MASSIMPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.tuempresa.com/webhooks/massimple",
"events": ["*"]
}'
Retornamos um secret que é exibido UMA vez. Guarde-o para verificar a assinatura HMAC de cada delivery.
* ou o preset rastreamento motorista (route.stop.status_changed, shipment.status_changed, route.optimized, route.status_changed). A lista de eventos não pode ser editada após criar o webhook.
X-MasSimple-Signature: t=<unix-time>,v1=<hmac-hex>. A assinatura é calculada sobre <timestamp>.<body-raw> usando seu secret (whsec_…). Verifique a assinatura sempre, sobre o body RAW (não parseado).
Fluxo no seu servidor:
- Você recebe
POST application/jsonna URL registrada. - Lê o body como string exata (bytes como chegaram).
- Parseia
t=ev1=do headerX-MasSimple-Signature. - Calcula
HMAC-SHA256(secret, "<t>.<body-raw>")em hex. - Compara com
v1(crypto.timingSafeEqual). Rejeite se faltar header, assinatura inválida ou timestamp > 5 min. - Se válido → parseia JSON → processa → responde
200.
Verifique a assinatura de cada webhook
Função de verificação + handler Express de referência. Variável de ambiente WEBHOOK_SECRET=whsec_….
import crypto from 'crypto';
export function verifySignatureHeader({ header, body, secret, maxAgeSeconds = 300 }) {
if (typeof header !== 'string') return false;
const parts = Object.fromEntries(
header.split(',').map((p) => {
const idx = p.indexOf('=');
return idx === -1 ? [p, ''] : [p.slice(0, idx).trim(), p.slice(idx + 1)];
}),
);
const ts = Number(parts.t);
if (!Number.isFinite(ts)) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - ts) > maxAgeSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${ts}.${body}`).digest('hex');
const provided = parts.v1 || '';
if (provided.length !== expected.length) return false;
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
}
Handler Express completo (use express.raw apenas nesta rota, não express.json() global):
import crypto from 'crypto';
import express from 'express';
const SECRET = process.env.WEBHOOK_SECRET;
function verifySignature(header, rawBody) {
return verifySignatureHeader({ header, body: rawBody, secret: SECRET });
}
const app = express();
app.post(
'/webhooks/massimple',
express.raw({ type: 'application/json' }),
(req, res) => {
const rawBody = req.body.toString('utf8');
const sig = req.headers['x-massimple-signature'];
if (!verifySignature(sig, rawBody)) {
return res.status(401).json({ error: 'invalid signature' });
}
const event = JSON.parse(rawBody);
console.log('Evento verificado:', event.type, event.data);
return res.status(200).json({ received: true });
},
);
Debug manual: echo -n "$TS.$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex — o hex deve coincidir com v1= do header.
Se o verificador retornar true, já pode parsear o body e processar o evento. Retorne 200 no final — qualquer 5xx dispara nosso retry exponencial (1m, 5m, 30m, 2h, 12h, 24h).
Auditoria: consulte o stream com GET /events
Complemento aos webhooks: pull em vez de push. Scope events:read. Lista o que o +simple emitiu (30 dias) mesmo que falhe o POST ao webhook ou você não tenha URL pública. Mesmo formato CloudEvents de um delivery (id, type, time, data).
| Query | Uso |
|---|---|
limit | Máx por página (default 50, máx 200) |
types | Filtro separado por vírgula, ex. shipment.status_changed,route.stop.status_changed |
after | Cursor next_cursor da página anterior |
curl "$MASSIMPLE_BASE/events?limit=50&types=route.stop.status_changed,shipment.status_changed" \
-H "Authorization: Bearer $MASSIMPLE_KEY"
{
"events": [
{
"id": "evt_6a16103d86b6e449bc2ffc76",
"type": "shipment.status_changed",
"source": "masSimple",
"specversion": "1.0",
"time": "2026-05-26T21:27:25.432Z",
"data": { "external_id": "ORD-1001", "current_status": "delivered" }
}
],
"next_cursor": "6a16103d86b6e449bc2ffc77"
}
Se next_cursor não for null, peça a próxima página com ?after=next_cursor. No demo Bazarshop (aba Eventos) você pode testar o polling com filtros sem curl. Detalhe em API Reference → Polling de eventos e Dashboard → Integrações → Endpoints.
Opere por external_id
Se você guarda seu próprio ID de pedido (ORD-1001), não precisa do shp_... do Massimple:
# Consultar
curl $MASSIMPLE_BASE/shipments/external/ORD-1001 \
-H "Authorization: Bearer $MASSIMPLE_KEY"
# Actualizar (solo content y monto a cobrar)
curl -X PATCH $MASSIMPLE_BASE/shipments/external/ORD-1001 \\
-H "Authorization: Bearer $MASSIMPLE_KEY" \\
-H "Content-Type: application/json" \\
-d '{"content": "Caja actualizada", "amount_to_collect": 1500}'
Listagem, atualização e exclusão por external_id usam as mesmas rotas com prefixo /shipments/external/.... O PATCH só aceita content e amount_to_collect (não destino, datas nem metadata). Para cancelar: POST /v1/shipments/{id}/cancel (ou .../external/{external_id}/cancel) com body opcional {"error_comment":"..."} — não é necessário enviar status. Emite shipment.status_changed e shipment.cancelled. O status failed não está na API pública: só o motorista marca pela DriverApp. Para recriar um envio cancelled ou failed: POST /v1/shipments/{id}/recreate (ou .../external/{external_id}/recreate) com {"scheduled_date":"YYYY-MM-DD"} — cria um envio novo em pending com os mesmos dados; o original não muda. Emite shipment.created. Para alta em massa: POST /v1/shipments/bulk e POST /v1/shipments/bulk-delete (ambos aceitam Idempotency-Key).
# Cancelar envío (por id o external_id)
curl -X POST $MASSIMPLE_BASE/shipments/external/ORD-1001/cancel \\
-H "Authorization: Bearer $MASSIMPLE_KEY" \\
-H "Content-Type: application/json" \\
-d '{"error_comment": "Cliente solicitó anulación"}'
# Recrear envío cancelado/fallido (nuevo pending, misma data)
curl -X POST $MASSIMPLE_BASE/shipments/external/ORD-1001/recreate \\
-H "Authorization: Bearer $MASSIMPLE_KEY" \\
-H "Content-Type: application/json" \\
-d '{"scheduled_date": "2026-06-10"}'
Envios sem rota atribuída: GET /v1/shipments/available (pending, sem assigned_route_id).
Consulte o que está livre para atribuir
Não há endpoints separados /unassigned. Use filtros nos listados:
| Recurso | Request | O que retorna |
|---|---|---|
| Envios para rotear | GET /shipments/available | Pendentes sem rota |
| Após excluir uma rota | GET /shipments/available | Os envios da rota excluída voltam a pending |
| Rotas sem motorista/veículo | GET /routes?has_assignment=false | Sem atribuição ativa |
| Rotas prontas para atribuir | GET /routes?has_assignment=false&status=optimized | Otimizadas, sem frota |
| Motoristas livres | GET /drivers?available_only=true | Ativos, sem veículo atribuído |
| Veículos livres | GET /vehicles?available_only=true | Disponíveis, sem motorista |
# Rutas sin asignación (ejemplo)
curl "$MASSIMPLE_BASE/routes?has_assignment=false&status=optimized&limit=50" \
-H "Authorization: Bearer $MASSIMPLE_KEY"
Detalhe de cada query param em API Reference → Listar rotas.
Crie e otimize uma rota
Com scopes routes:write e routes:optimize. Você pode montar uma rota a partir de envios pendentes:
curl -X POST $MASSIMPLE_BASE/routes \
-H "Authorization: Bearer $MASSIMPLE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"external_id": "ROUTE-2026-001",
"name": "Ruta mañana CABA",
"shipment_external_ids": ["ORD-1001", "ORD-1002"],
"optimize": true
}'
Resposta 201 com id (rte_...), status (ex. optimized) e stops_count. Para adicionar ou remover envios em uma rota existente: PATCH /v1/routes/:id com add_shipment_ids / remove_shipment_ids (apenas draft/optimized sem assignment). Para reordenar paradas: PATCH /v1/routes/:id/stops/sequence. Para otimizar depois: POST /v1/routes/:id/optimize.
routes:delete. Apenas draft ou optimized sem assignment ativo. Se a rota está atribuída → primeiro POST /v1/routes/:id/unassign, depois DELETE. Resposta 204 sem body.
pending com assigned_route_id null (mesmo que na rota estivessem em assigned). Veja-os novamente com GET /v1/shipments/available e monte outra rota com POST /v1/routes.
# Borrar ruta (sin assignment)
curl -i -X DELETE "$MASSIMPLE_BASE/routes/rte_507f1f77bcf86cd799439011" \
-H "Authorization: Bearer $MASSIMPLE_KEY"
# Por external_id del ERP
curl -i -X DELETE "$MASSIMPLE_BASE/routes/external/ROUTE-2026-001" \
-H "Authorization: Bearer $MASSIMPLE_KEY"
Com assignment na rota, DELETE responde 409. Detalhe em API Reference → Rotas: excluir, desatribuir e liberar frota.
Frota: veículo e motorista
Scopes vehicles:* e drivers:*. Mesmos padrões que envios: CRUD, external_id e bulk.
# Vehículo
curl -X POST $MASSIMPLE_BASE/vehicles \
-H "Authorization: Bearer $MASSIMPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "VEH-01",
"license_plate": "AB123CD",
"distribution_center_id": "dc_507f1f77bcf86cd799439012"
}'
# Conductor (sin password → temporary_password en la respuesta)
curl -X POST $MASSIMPLE_BASE/drivers \
-H "Authorization: Bearer $MASSIMPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "DRV-01",
"document": "30111222",
"phone": "+5491112345678",
"license_number": "LIC-123",
"license_type": "B"
}'
A resposta de criar motorista pode incluir temporary_password uma única vez. Guarde-a para o primeiro login na DriverApp. Se você enviar password, não é retornada.
DELETE /v1/vehicles/:id e DELETE /v1/drivers/:id respondem 204 sem body se o recurso estiver livre (sem atribuição ativa). No seu cliente não parseie JSON: use res.ok ou status 204. Se houver assignment → 409.
Editar recursos (PATCH)
Cada PATCH só aceita campos específicos; outros campos respondem 400. Detalhe em API Reference → Edição parcial.
| Recurso | Campos | Notas |
|---|---|---|
| Envio (PATCH) | content, amount_to_collect | Não se delivered/cancelled/failed |
| Envio (cancelar) | POST .../cancel — error_comment opcional | Só cancelled; failed é só DriverApp |
| Envio (recriar) | POST .../recreate — scheduled_date obrigatório | Só se origem cancelled/failed; novo pending; emite shipment.created |
| Veículo | mileage | Obrigatório |
| Motorista | phone, license_number, preferred_areas, experience | Não nome nem documento |
| Rota | add_shipment_*, remove_shipment_* | Sem assignment; envios de /shipments/available |
# Vehículo — solo kilometraje
curl -X PATCH $MASSIMPLE_BASE/vehicles/external/VEH-01 \\
-H "Authorization: Bearer $MASSIMPLE_KEY" \\
-H "Content-Type: application/json" \\
-d '{"mileage": 45200}'
# Ruta — agregar/quitar envíos
curl -X PATCH $MASSIMPLE_BASE/routes/external/ROUTE-2026-001 \\
-H "Authorization: Bearer $MASSIMPLE_KEY" \\
-H "Content-Type: application/json" \\
-d '{"add_shipment_external_ids":["ORD-1003"],"remove_shipment_external_ids":["ORD-1001"]}'
Atribua motorista e veículo a uma rota
Scope assignments:write. Motorista e veículo devem estar no mesmo centro de distribuição e disponíveis.
curl -X POST $MASSIMPLE_BASE/assignments \
-H "Authorization: Bearer $MASSIMPLE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"external_id": "ASG-001",
"driver_external_id": "DRV-01",
"vehicle_external_id": "VEH-01",
"route_external_id": "ROUTE-2026-001"
}'
Antes de atribuir, você pode listar recursos livres (passo 6b). Para liberar frota: se a atribuição tem rota, primeiro POST /v1/routes/:id/unassign (rota assigned, sem ter iniciado). Isso desvincula a rota e deixa motorista+veículo na atribuição (active). Depois POST /v1/assignments/:id/unassign só se route_id for null — com rota vinculada o endpoint de atribuição responde 409. A rota otimizada volta a listar com GET /routes?has_assignment=false.
Execução em rota (app motorista)
Após POST /assignments, o motorista opera a rota pela DriverApp (sliders). Isso não usa endpoints públicos /v1: é API corp / SDUI interna (PUT /api/corp/routes/:id/change-status-to-stop, ação deliver_current_stop).
pending conforme o tráfego (Google Routes). Se a ordem mudar, você recebe route.optimized no seu webhook.
Webhooks de rastreamento. Cada slider do motorista emite route.stop.status_changed e shipment.status_changed. Ao completar a última parada, route.status_changed para completed. O payload da parada inclui tracking_number, external_id, stop_type, sequence, previous_status e current_status.
| Ação motorista | Eventos webhook típicos |
|---|---|
| Começar / a caminho | route.stop.status_changed, shipment.status_changed (in_transit) |
| Entrega concluída | Igual + possível route.optimized |
| Última parada | + route.status_changed → completed |
Auditoria: GET /v1/events?types=route.stop.status_changed,shipment.status_changed (events:read) lista o emitido mesmo que falhe o POST ao webhook.
Consulte a sequência atualizada com GET /v1/routes/:id/stops. Detalhe em API Reference → Execução em rota e Dashboard → Integrações → Endpoints.
cd backend && npm run sync-openapi-v2 antes de publicar a web.