Developers · API v1

Lyverto API

Connect the systems you already run. Send consignments and shipment updates in — in your own format if you like — and get every event out, by webhook or by polling.

Base URL: https://lyverto.com/api/v1

Get started

Overview

The Lyverto API connects the systems you already run — a TMS, a warehouse spreadsheet, a carrier feed, Zapier — to Lyverto. Lyverto does not replace them. It carries data in (consignments and shipment updates, in your own format if you like) and out (to your customers’ tracking pages, email and WhatsApp, to your partners, and back to your tools as events).

Base URLhttps://lyverto.com/api/v1
FormatJSON over HTTPS. Times are ISO 8601 in UTC.
AuthenticationAuthorization: Bearer <API key>
Machine-readableOpenAPI 3.1 · This page as Markdown

What you can build

  • Sync consignments from your TMS or spreadsheet, and keep them in step as they change.
  • Push shipment updates — one consignment or a whole container — with the reason and next step customers plan around.
  • Send your own JSON through a mapping you build once, without changing your system.
  • Feed carrier events in the DCSA Track & Trace format.
  • React to events by webhook or by polling the event stream.
  • Power your own website with consignment status and ready-made customer tracking links.
  • Automate without code in Zapier, Make, n8n or Power Automate.
Get started

Quickstart

  1. In Lyverto, open Settings → Integrations → API keys and create a key with the tracking:write and tracking:read scopes. Copy it — it is shown once.
  2. Check the key:
curl https://lyverto.com/api/v1/me \
  -H "Authorization: Bearer lyv_YOUR_API_KEY"
{
  "success": true,
  "data": {
    "organizationId": "<uuid>",
    "scopes": ["tracking:write", "tracking:read"],
    "keyId": "<uuid>"
  }
}
  1. Create your first consignment:
curl -X POST https://lyverto.com/api/v1/consignments \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "LC-0142",
    "customerName": "Ama Mensah",
    "customerPhone": "+233 24 123 4567",
    "consolidationRef": "MSKU1234567",
    "destination": "Tema",
    "publish": true
  }'

The customer now has a live tracking page (trackingUrl in the response) and, because you published it, has been notified.

Get started

Authentication

Every request except GET /openapi.json needs an API key, sent as a bearer token (or in an X-Api-Key header). Keys start with lyv_ and act for one business — nothing in a request body can name another.

ScopeAllows
tracking:writeCreate and update consignments, submit updates, run mappings, send carrier feeds
tracking:readRead consignments, their timelines and your mappings
events:readRead the event stream
webhooks:manageSubscribe and unsubscribe webhooks (used by Zapier and Make instant triggers)
Keep keys server-side. Never put one in a browser, a mobile app or a public repository. If a key leaks, revoke it in Settings → Integrations; revoking also removes every webhook it subscribed.
Get started

Conventions

Responses

Successful responses wrap the result in data. Errors carry a human-readable error and a stable machine-readable reason.

{ "success": true, "data": { … } }

Request IDs

Every response has a Request-Id header (req_…). It is recorded in your request log; quote it when you contact support.

Batches and per-item results

Endpoints that take many items (/events, /ingest/{mapping}, /feeds/dcsa) answer one result per item, in order. One bad item never fails the batch: it comes back skipped with a reason, and the rest are written.

{
  "success": true,
  "data": [
    { "index": 0, "status": "proposed", "proposalId": "<uuid>" },
    { "index": 1, "status": "duplicate", "id": "<uuid>" },
    { "index": 2, "status": "skipped", "reason": "no consignment or container of yours matches the target" }
  ]
}
StatusMeaning
created / updatedA consignment was written
proposedThe update is in your Review queue, waiting for a person (or an automation) to approve it
appendedThe update was written to the timeline directly
duplicateThis externalEventId was already received; nothing was written
skippedThe item could not be used; reason says why

Safe retries

Give every update an externalEventId (your own id for it). Sending the same id again is answered duplicate, so a retry after a timeout never records an event twice. Consignments are keyed by reference, so re-sending one updates it instead of creating a copy.

Dates

Send ISO 8601 (2026-10-20T08:00:00Z). Through a mapping, 20/10/2026 (day first) and Unix seconds or milliseconds are also understood.

Get started

Plans and rate limits

Limits count per business, across all of its keys. The free plan is meant for real use: one system or a couple of zaps, every day.

PlanRequests / minuteRequests / dayAPI keysWebhooksMappingsItems per batch
Free602,00022350
Starter30025,00051015200
Professional1,200250,0002550100500
Enterprise3,0002,000,0001002005001,000

Every response tells you where you stand:

Request-Id: req_4f0c2b9e7a1d3c5e8f6a2b1c
RateLimit-Limit: 60
RateLimit-Remaining: 57
RateLimit-Reset: 42
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1790640060
X-Quota-Limit: 2000
X-Quota-Remaining: 1873
X-Plan: free

Over a limit you get 429 with Retry-After (seconds) and a reason: rate_limited means slow down and retry; quota_exceeded means the day’s quota is used (it resets at 00:00 UTC).

{
  "error": "Too many requests: your plan allows 60 per minute. Retry in 18s.",
  "reason": "rate_limited",
  "requestId": "req_…"
}
Guides

Sync consignments from your system

Use case: your TMS or spreadsheet is where bookings start. Push each one to Lyverto so the customer gets a tracking page, and push changes as they happen.

POST /consignments creates a consignment, or updates the one with the same reference. Only the fields you send are changed — a sync that does not know the customer’s email never erases the one your team typed.

curl -X POST https://lyverto.com/api/v1/consignments \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reference": "LC-0142", "eta": "2026-10-24T00:00:00Z" }'

Moving a consignment along

Change the status with PATCH /consignments/{reference}. Moving a draft to published, in_transit or delivered publishes it first, and the customer is told once. A published consignment cannot go back to draft — its customer has already seen it.

curl -X PATCH https://lyverto.com/api/v1/consignments/LC-0142 \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "delivered" }'
Guides

Push shipment updates

Use case: the container departed, a consignment is held at customs, the ETA moved. Tell Lyverto once and every affected customer can see it — with the reason and the next step, which is what they plan their cash, trucks and stock around.

curl -X POST https://lyverto.com/api/v1/events \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "target": { "reference": "LC-0142" },
      "externalEventId": "tms-88121",
      "label": "Customs hold",
      "code": "exception",
      "reason": "Physical inspection requested on HS 8517",
      "nextStep": "Clearance expected in 3–5 working days. No action needed from you.",
      "location": "Tema",
      "eta": "2026-10-24T00:00:00Z"
    }]
  }'

Review or write directly

By default ("mode": "propose") updates wait in your Review queue until a person approves them — or an automation you set up does. Use "mode": "append" when the update comes from your own system and should go straight onto the timeline.

codeUse for
picked_upReceived at origin or collected
in_transitMoving: loaded, departed, arrived, discharged
customs_clearanceCustoms events and releases
out_for_deliveryOn the last leg
deliveredHanded over
exceptionHolds, damage, delays
Guides

Send your own JSON with a mapping

Use case: your system already produces JSON in its own shape, and changing it is not an option. Build a mapping once, point your system at its URL, and send the JSON as it is.

  1. In Settings → Integrations → Data mappings, choose New mapping, paste one sample of what your system sends, and choose Read sample.
  2. For each Lyverto field, pick which of your fields fills it — or a fixed value, or a combination such as {{status}} at {{port}}. Translate your own codes with a lookup (DEP=in_transit).
  3. Preview, then Save. You get a URL like https://lyverto.com/api/v1/ingest/warehouse-sheet.
curl -X POST https://lyverto.com/api/v1/ingest/warehouse-sheet \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rows": [
      { "Ref No": "LC-0142", "Client": "Ama Mensah", "Container": "msku1234567", "ETA": "20/10/2026" },
      { "Ref No": "", "Client": "Unknown" }
    ]
  }'

Add ?dryRun=true to see what each item would become without writing anything — useful while you wire up a new source.

{
  "success": true,
  "dryRun": true,
  "data": [
    { "index": 0, "ok": true, "value": { "reference": "LC-0142", "customerName": "Ama Mensah", "consolidationRef": "MSKU1234567", "eta": "2026-10-20T00:00:00.000Z" } },
    { "index": 1, "ok": false, "errors": ["Reference is empty."] }
  ]
}
Guides

Feed carrier events (DCSA)

Use case: you receive container events from a shipping line or a tracking aggregator. Forward them in the DCSA Track & Trace format; Lyverto matches each to your container by equipmentReference (or a document reference) and proposes a customer-friendly update.

curl -X POST https://lyverto.com/api/v1/feeds/dcsa \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[{
    "eventID": "7c1e…",
    "eventType": "EQUIPMENT",
    "eventClassifierCode": "ACT",
    "equipmentEventTypeCode": "DISC",
    "eventDateTime": "2026-10-19T22:10:00Z",
    "equipmentReference": "MSKU1234567",
    "eventLocation": { "locationName": "Tema", "UNLocationCode": "GHTEM" }
  }]'

That event becomes the proposal “Discharged from vessel at Tema (GHTEM)”. Estimated vessel arrivals (EST + ARRI) become ETA-change proposals. To skip review for routine events, turn on the “Approve routine carrier events automatically” automation in Lyverto.

Guides

React to events: webhooks and polling

Use case: your system should know when something happens in Lyverto — a customer was told, an update was approved, a customer sent a WhatsApp message.

Webhooks (push)

Add an endpoint in Settings → Integrations → Webhooks (or subscribe with POST /hooks). Lyverto POSTs batches of events, in order, and resumes where it stopped if your endpoint is down. Delivery is at least once: respond 2xx quickly and de-duplicate on seq.

{
  "id": "<delivery-uuid>",
  "organizationId": "<uuid>",
  "sentAt": "2026-09-29T08:10:05.000Z",
  "events": [
    {
      "seq": 1042,
      "hash": "<sha256>",
      "action": "tracking.event.appended",
      "subjectType": "tracking_record",
      "subjectId": "<uuid>",
      "summary": "Added checkpoint \"Customs hold\" on consignment LC-0142",
      "payload": { "reference": "LC-0142", "label": "Customs hold", "reason": "Physical inspection requested", "nextStep": "Clearance in 3–5 days", "…": "…" },
      "actorKind": "human",
      "source": "api",
      "occurredAt": "2026-09-29T08:10:00.000Z"
    }
  ]
}

Verify every delivery. The Lyverto-Signature header is t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>"> using your endpoint’s signing secret. Reject signatures older than five minutes.

import crypto from "node:crypto";

export function verifyLyverto(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1 ?? "", "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Polling (pull)

No public endpoint? Poll GET /events with the cursor from your last call. Store cursor.after and pass it back as after.

curl "https://lyverto.com/api/v1/events?after=1041&limit=100" \
  -H "Authorization: Bearer lyv_YOUR_API_KEY"

Event types

actionHappens when
tracking.record.created / .updated / .publishedA consignment was created, edited, or made visible to its customer
tracking.event.appendedA timeline update was recorded (by a person, an agent, a feed or your API)
tracking.broadcast.withdrawnA container update was taken back
tracking.proposal.created / .approved / .rejectedAn update entered Review, or was decided
tracking.share.created / .revokedA consignment was forwarded to a partner, or access withdrawn
tracking.customer.notifiedAn automation told a customer
channel.message.receivedA customer sent a WhatsApp message
tracking.rule.*, integration.*Automations, keys, webhooks, mappings or WhatsApp settings changed
Guides

Power your own website

Use case: keep your own site and portal, and use Lyverto as the backend for shipment status. Each consignment carries trackingUrl — the customer’s live tracking page — once published, so you can link to it, or read the status and timeline and show them in your own design.

curl https://lyverto.com/api/v1/consignments/LC-0142 \
  -H "Authorization: Bearer lyv_YOUR_API_KEY"
Call the API from your server, never from the visitor’s browser — the key must stay secret. Cache responses for a minute or two to stay well inside your plan.
Guides

Zapier, Make, n8n and Power Automate

Use case: connect Lyverto to Google Sheets, a CRM, email or accounting without writing code.

  • Zapier: use the Lyverto app. Triggers: *New Event* (instant) and *New or Updated Consignment*. Actions: *Create or Update Consignment*, *Add Shipment Update*, *Send Data Through a Mapping*. Search: *Find Consignment*. Give its key the webhooks:manage scope for instant triggers.
  • Make, n8n, Power Automate, Postman: import the OpenAPI description at https://lyverto.com/api/v1/openapi.json, or use an HTTP step with your key.
  • Instant triggers in your own tool: subscribe a URL with POST /hooks and remove it with DELETE /hooks/{id}.
curl -X POST https://lyverto.com/api/v1/hooks \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.example.com/catch/123", "events": ["tracking.event.appended"] }'
Reference

Account

GET/meany key

The business and scopes this key acts for. Use it as a connection test.

curl https://lyverto.com/api/v1/me -H "Authorization: Bearer lyv_YOUR_API_KEY"
GET/usageany key

Your plan, its limits, and today’s usage.

curl https://lyverto.com/api/v1/usage -H "Authorization: Bearer lyv_YOUR_API_KEY"
Reference

Consignments

GET/consignmentsscope: tracking:read

Your consignments, most recently changed first. For polling, pass the time of your last poll as updatedSince.

Query parameters

status string
draft, published, in_transit, delivered, on_hold or cancelled
consolidationRef string
Only this container or master bill
updatedSince ISO date-time
Only consignments changed after this moment
limit integer
1–100, default 50
curl "https://lyverto.com/api/v1/consignments?status=in_transit&limit=20" \
  -H "Authorization: Bearer lyv_YOUR_API_KEY"
POST/consignmentsscope: tracking:write

Create a consignment, or update the one with this reference. Only the fields you send are changed.

Body

reference stringrequired
Your reference (max 100)
description string
What the goods are
customerName string
—
customerEmail string
Where email updates go
customerPhone string
WhatsApp number with country code, e.g. +234 803 123 4567
consolidationRef string
Container or master bill number
origin string
—
destination string
—
eta ISO date-time
—
status string
See the lifecycle rules in the guide
publish boolean
On creation: make it visible to the customer and notify them
curl -X POST https://lyverto.com/api/v1/consignments \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reference": "LC-0142", "customerName": "Ama Mensah", "publish": true }'
GET/consignments/{reference}scope: tracking:read

One consignment, with its customer-visible timeline in events.

curl https://lyverto.com/api/v1/consignments/LC-0142 -H "Authorization: Bearer lyv_YOUR_API_KEY"
  • 404 with reason: not_found when you have no consignment with that reference.
PATCH/consignments/{reference}scope: tracking:write

Update the fields you send. Same fields as POST /consignments, without reference and publish.

curl -X PATCH https://lyverto.com/api/v1/consignments/LC-0142 \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "in_transit", "eta": "2026-10-24T00:00:00Z" }'
  • 400 with reason: validation_failed when moving a published consignment back to draft.
GET/consignments/{reference}/eventsscope: tracking:read

Just the customer-visible timeline, oldest first.

curl https://lyverto.com/api/v1/consignments/LC-0142/events -H "Authorization: Bearer lyv_YOUR_API_KEY"
Reference

Updates and the event stream

POST/eventsscope: tracking:write

Submit timeline updates. Each goes to Review (propose, the default) or straight to the timeline (append).

Body

mode "propose" | "append"
Default propose
events[].target objectrequired
Exactly one of reference, consolidationRef or trackingId
events[].label stringrequired
What happened (max 200)
events[].externalEventId string
Your id — makes retries safe
events[].code string
See the codes table in the guide
events[].reason string
Why it happened — shown to the customer
events[].nextStep string
What happens next — shown to the customer
events[].occurredAt ISO date-time
Defaults to now
events[].location string
—
events[].latitude / longitude number
Both or neither
events[].eta ISO date-time
A revised ETA
events[].standardCode string
Industry code, e.g. dcsa:DISC
curl -X POST https://lyverto.com/api/v1/events \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "events": [{ "target": { "reference": "LC-0142" }, "externalEventId": "tms-1", "label": "Picked up", "code": "picked_up" }] }'
  • 400 with reason: batch_too_large over your plan’s batch size.
GET/eventsscope: events:read

Everything that happened in your business, in order, by cursor.

Query parameters

after integer
The cursor.after from your previous call (0 to start)
limit integer
1–500, default 100
curl "https://lyverto.com/api/v1/events?after=0" -H "Authorization: Bearer lyv_YOUR_API_KEY"
Reference

Mappings

POST/ingest/{mapping}scope: tracking:write

Send any JSON — an object, an array, or an object holding the array — through a saved mapping.

Query parameters

dryRun boolean
true to see the mapped items without writing
curl -X POST "https://lyverto.com/api/v1/ingest/warehouse-sheet?dryRun=true" \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "rows": [{ "Ref No": "LC-0142", "Client": "Ama Mensah" }] }'
  • 404 when no mapping has that name; 409 with reason: mapping_paused when it is paused.
GET/mappingsscope: tracking:read

Your saved mappings.

curl https://lyverto.com/api/v1/mappings -H "Authorization: Bearer lyv_YOUR_API_KEY"
Reference

Carrier feeds

POST/feeds/dcsascope: tracking:write

DCSA Track & Trace events (a bare array, or { "events": [...] }). Each is matched to your container or consignment and proposed.

curl -X POST https://lyverto.com/api/v1/feeds/dcsa \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[{ "eventID": "…", "eventType": "TRANSPORT", "eventClassifierCode": "ACT", "transportEventTypeCode": "DEPA", "eventDateTime": "2026-09-29T06:00:00Z", "equipmentReference": "MSKU1234567" }]'
  • Unmatched or non-customer-facing events come back skipped with the reason.
Reference

Webhook subscriptions

POST/hooksscope: webhooks:manage

Subscribe a URL to events. Omit events for everything. The signing secret is returned once.

Body

url stringrequired
Public https:// URL
events string[]
Actions to receive, e.g. ["tracking.event.appended"]
curl -X POST https://lyverto.com/api/v1/hooks \
  -H "Authorization: Bearer lyv_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://your-system.example.com/lyverto" }'
DELETE/hooks/{id}scope: webhooks:manage

Unsubscribe. A key can only remove hooks it created.

curl -X DELETE https://lyverto.com/api/v1/hooks/<id> -H "Authorization: Bearer lyv_YOUR_API_KEY"
Reference

Errors

HTTPreasonWhat to do
400validation_failedFix the field named in details or the message
400batch_too_largeSplit the batch to your plan’s size
401invalid_api_keyCheck the key; it may be revoked or expired
403scope_requiredCreate a key with the scope named in the message
404not_foundNothing of yours matches
409conflict / mapping_pausedThe current state prevents it — read the message
429rate_limitedWait Retry-After seconds, then retry
429quota_exceededDaily quota used; resets 00:00 UTC, or upgrade
500—Retry with backoff; quote the Request-Id if it persists

Fields the API does not recognise are ignored, not refused, so an extra field never breaks a request. The response lists each one in warnings — for example { "field": "custmerEmail", "message": "Unknown field; ignored." } — so a typo shows up in your logs instead of silently doing nothing.

© 2026 LyvertoTermsPrivacy