Create legally valid electronic consignment notes, collect eIDAS-compliant signatures and receive signed PDFs — all over a JSON API your TMS or ERP can integrate in an afternoon.
# 1. Create an account — no API key needed
curl -X POST https://ecmr.dev.40pes.pt/api/v1/ecmr/signup \
-H "Content-Type: application/json" \
-d '{ "email": "dev@acme.io", "company_name": "ACME Logistics", "country_code": "PT" }'
# 2. Click the link in the email, then read your key
curl "https://ecmr.dev.40pes.pt/api/v1/ecmr/signup/verify?token=<TOKEN>"
# → { "success": true, "data": { "api_key": "…", "rate_limit_per_minute": 60 } }
# 3. Create your first eCMR
curl -X POST https://ecmr.dev.40pes.pt/api/v1/ecmr/documents \
-H "X-API-Key: $ECMR_API_KEY" \
-H "Content-Type: application/json" \
-d @ecmr.json
# → 201 { "success": true, "data": { "cmr_number": "…", "status": "draft" } }
Built on the standards your customers audit
Fill in five fields and we create your account. Everything is free until the end of 2026 — no card, no sales call.
Quick start
No sales call, no sandbox request form. Create an account from the API, verify your email and start creating documents.
One public POST with your company details. No API key needed yet — we send a verification link to the email you provide.
{
"email": "dev@acme.io",
"company_name": "ACME Logistics",
"vat_number": "PT123456789",
"company_type": "carrier",
"country_code": "PT"
}
Open the link (or call the verify endpoint with the token). The response contains your API key and your rate limit — 60 requests per minute by default.
{
"success": true,
"data": {
"tenant_id": "tnt_8f2c…",
"api_key": "ecmr_live_…",
"status": "active",
"rate_limit_per_minute": 60
}
}
Send the consignor, carrier, consignee, goods and transport data. The document is created as a draft and validated against the CMR field rules.
curl -X POST https://ecmr.dev.40pes.pt/api/v1/ecmr/documents \
-H "X-API-Key: $ECMR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"document": {
"external_reference": "TMS-2026-001",
"consignor": { "name": "Shipper Lda", "address": "Lisboa, PT", "country": "PT" },
"carrier": { "name": "ACME Logistics", "address": "Porto, PT", "country": "PT" },
"consignee": { "name": "Receiver GmbH", "address": "Berlin, DE", "country": "DE" },
"goods": [{ "description": "Electronic components", "quantity": 10, "packaging": "pallets", "weight_kg": 2500 }],
"transport": { "vehicle_registration": "12-AB-34", "loading_date": "2026-09-10", "delivery_date": "2026-09-12" }
}
}'
Issue the draft, then request signatures per role. Signatories receive a link by SMS or email, validate a one-time code and sign on their phone. Your webhook fires at every state change.
# Issue the document (draft → issued)
curl -X POST https://ecmr.dev.40pes.pt/api/v1/ecmr/documents/$CMR_NUMBER/lifecycle/issue \
-H "X-API-Key: $ECMR_API_KEY"
# Ask the consignor to sign: they get a link by SMS or email and validate an OTP
curl -X POST https://ecmr.dev.40pes.pt/api/v1/ecmr/documents/$CMR_NUMBER/signatures \
-H "X-API-Key: $ECMR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "signatory_role": "consignor", "signatory_name": "Maria Silva", "contact": "+351912345678", "signature_type": "remote" }'
# Repeat for carrier and consignee — your webhook gets `completed` when all three have signed
Every request above is in the Postman collection, with variables populated automatically as you go: Postman
Postman
A ready-to-run Postman collection with every endpoint, realistic request bodies and test scripts that carry your API key, tenant and CMR number from one request to the next.
API reference
Everything is JSON over HTTPS, authenticated with an X-API-Key header. Every response uses the same envelope, every error carries a machine-readable code.
Create, read, update, search and download eCMRs as PDF.
/documents
/documents
/documents/:cmr_number
/documents/:cmr_number
/documents/:cmr_number/pdf
/documents/search?q=
Move a document from draft to issued, cancel it, and audit its history and compliance.
/documents/:cmr_number/lifecycle/issue
/documents/:cmr_number/lifecycle/cancel
/documents/:cmr_number/lifecycle/status
/documents/:cmr_number/history
/documents/:cmr_number/compliance
Request signatures per role, validate one-time codes and record the signature.
/documents/:cmr_number/signatures
…/signatures/:id/validate_otp
…/signatures/:id/sign
…/signatures/:id/resend_otp
…/signatures/:id/cancel
Tokenised links and QR codes so drivers, warehouses and authorities can view or sign.
/documents/:cmr_number/shares
/documents/:cmr_number/shares/active
…/shares/generate_qr
…/shares/:token/revoke
/signatures/public/:token
Profile, usage statistics and your webhook endpoint configuration.
/tenant/profile
/tenant/profile
/tenant/usage
/tenant/webhook
/tenant/test_webhook
For integrators managing many customers: create and manage tenants and read usage.
/reseller/signup
/reseller/tenants
/reseller/tenants
/reseller/tenants/:id/suspend
/reseller/billing/usage
All paths are relative to https://ecmr.dev.40pes.pt/api/v1/ecmr
Successful responses always return success: true, a data object and a meta block with the server timestamp.
{
"success": true,
"data": {
"cmr_number": "CMR-2026-000123",
"status": "draft",
"external_reference": "TMS-2026-001"
},
"meta": { "timestamp": "2026-09-04T10:32:11Z" }
}
Failed responses return success: false and an error with a stable code, a human message and per-field details. Map the code, not the message.
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "Validation failed",
"details": ["Consignor name can't be blank"]
},
"meta": { "timestamp": "2026-09-04T10:32:11Z" }
}
Configure one URL and a secret on your tenant. We POST a JSON payload on every state change and sign the raw body with HMAC-SHA256 so you can reject anything that did not come from us.
Events
issuedaccepteddeliveredcompletedcancelledimport { createHmac, timingSafeEqual } from "node:crypto"
const expected = createHmac("sha256", process.env.ECMR_WEBHOOK_SECRET)
.update(rawBody)
.digest("hex")
const received = req.headers["x-webhook-signature"] ?? ""
const valid = timingSafeEqual(Buffer.from(expected), Buffer.from(received))
Content-Type: application/json
X-eCMR-Event: completed
X-Webhook-Signature: 3f9a1c… // HMAC-SHA256 hex of the raw body
{
"event": "completed",
"tenant_id": "tnt_8f2c…",
"document": {
"cmr_number": "CMR-2026-000123",
"status": "completed",
"external_reference": "TMS-2026-001"
},
"data": {},
"timestamp": "2026-09-04T14:05:42Z"
}
Send your key in the X-API-Key header on every request. Keys are per tenant and can be regenerated at any time without downtime.
60 requests per minute per tenant by default, returned as HTTP 429 with code RATE_LIMIT_EXCEEDED when exceeded. Higher limits on request.
PDFs, notification emails and signature pages are available in pt, en, es, de, fr and it. Pass locale on the PDF endpoint or preferred_locale when requesting a signature.
Security & compliance
A digital consignment note is only useful if it holds up in an audit or in court. These guarantees are built into every document, not bolted on.
Each signatory validates a code sent by SMS or email before signing, tying the signature to a verified contact and protecting against impersonation.
Every signature is stamped by a certified time-stamping authority, proving the exact date and time in a legally binding way.
Signatures follow the eIDAS Regulation (EU 910/2014): they identify the signer, detect later changes and prevent repudiation. Qualified certificates (QES) are supported.
Every action is appended to an audit log that nobody can edit — who, what, when and from where. Versions are kept whenever a document changes.
Each document carries a unique hash and a platform signature under our own certificate, so tampering with a single character is detected immediately.
Data minimisation, TLS in transit, encryption at rest, and automatic archiving for the legal retention period with deletion afterwards.
From the makers of 40 PÉS
40 PÉS is integrated management software for freight transport, built in Portugal: services, fleet tracking, billing, driver communication and CMR documents in one place. The eCMR API on this page is the same engine that powers it — and if you would rather not build the integration yourself, 40 PÉS already has it.
Grab the collection, create your account from the API and ship your first eCMR before the next stand-up.
Live API status: /api/v1/ecmr/health