Developers

The Kelvara API

One REST API for shipments, routes, drivers, lockers and tracking events. JSON over HTTPS, predictable resources, idempotent writes and signed webhooks. Sandbox credentials are issued with every trial account.

Quick start
# 1. Create a shipment in the sandbox
curl https://api.sandbox.dpd-lt.com/v1/shipments \
  -H "Authorization: Bearer $KELVARA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @shipment.json

# 2. Watch it move
curl https://api.sandbox.dpd-lt.com/v1/shipments/shp_9F41CX22/events \
  -H "Authorization: Bearer $KELVARA_API_KEY"
Conventions

How the API behaves

There are no surprises here on purpose. If you have integrated a modern payments or messaging API, this will feel familiar.

Base URLs

Production is api.dpd-lt.com and sandbox is api.sandbox.dpd-lt.com. Sandbox is a full copy of the platform with simulated vehicles that move in accelerated time, so you can watch a route complete in minutes.

Authentication

A bearer token in the Authorization header. Keys are scoped per environment and per permission set — a key that can read tracking events cannot create shipments unless you grant it. Rotate keys from the console at any time; the previous key stays valid for a 24-hour overlap.

Versioning

The version is in the path (/v1/). Additive changes — new fields, new event types, new optional parameters — ship without a version bump, so parse responses tolerantly. Breaking changes get a new version and a minimum 12-month overlap.

Idempotency

Every POST accepts an Idempotency-Key header. Replaying a request with the same key returns the original response instead of creating a duplicate shipment. Keys are retained for 48 hours — long enough to survive a retry storm during peak.

Pagination

Cursor-based. List endpoints return data plus a next_cursor; pass it back as ?cursor=. Offset pagination is not supported because operational data moves underneath you while you page through it.

Rate limits

60 requests per second on Starter, 250 on Growth, negotiated on Enterprise. Every response carries X-RateLimit-Remaining and X-RateLimit-Reset. Exceeding the limit returns 429 with a Retry-After header — honour it rather than spinning.

API keys are secrets. Keep them server-side, never in a browser bundle or mobile binary, and rotate immediately if one is exposed. Kelvara staff will never ask you for a key.
Reference

Core resources

Five resource families cover most integrations. Everything below is described in full in the OpenAPI document, which you can pull from api.dpd-lt.com/v1/openapi.json.

Shipments

A shipment is one consignment to one destination. It holds the recipient, the service level, one or more parcels, and the delivery preferences that constrain how it can be fulfilled.

MethodPathDescription
POST/v1/shipmentsCreate a shipment. Returns a tracking code and the resolved, normalised destination address.
GET/v1/shipments/{id}Retrieve a single shipment with its current status and ETA.
GET/v1/shipmentsList shipments, filterable by status, depot, date range or reference.
PATCH/v1/shipments/{id}Update delivery preferences or recipient contact details before dispatch.
DELETE/v1/shipments/{id}Cancel a shipment. Rejected once the parcel is loaded onto a vehicle.
GET/v1/shipments/{id}/eventsThe full ordered event history for a shipment.
GET/v1/shipments/{id}/proofProof-of-delivery record, including signed URLs for signature and photo media.

Routes & planning

Planning is asynchronous. You submit a plan request describing the depot, the vehicles available and the shipments to include; the optimiser returns a plan you can accept, adjust or discard.

MethodPathDescription
POST/v1/plansSubmit a planning run. Returns a plan ID immediately; completion arrives by webhook.
GET/v1/plans/{id}Plan status, summary metrics and the resulting routes once solved.
POST/v1/plans/{id}/commitAccept a plan and release its routes to drivers.
GET/v1/routes/{id}A single route with its ordered stops, assigned vehicle and driver.
POST/v1/routes/{id}/replanReoptimise the remaining stops on an in-progress route.

Lockers

MethodPathDescription
GET/v1/lockersList cabinets, filterable by proximity to a coordinate or postal code.
GET/v1/lockers/{id}/availabilityFree compartments by size class, refreshed continuously.
POST/v1/lockers/{id}/reservationsHold a compartment for a shipment ahead of dispatch.
DELETE/v1/reservations/{id}Release a held compartment back to the pool.

Addresses

MethodPath
POST/v1/addresses/resolve
GET/v1/addresses/autocomplete

Resolve takes a free-text string and returns a canonical Baltic address with a confidence score, or a list of candidates when the input is genuinely ambiguous.

Fleet & drivers

MethodPath
GET/v1/vehicles
POST/v1/vehicles
GET/v1/drivers
GET/v1/drivers/{id}/shifts

Vehicles carry capability flags — cooling, tail lift, capacity by volume and weight — that the optimiser treats as hard constraints.

Example

Creating a shipment

A minimal create call and the response you get back. Note that the address you send is echoed back normalised, with a confidence score you can act on before the parcel is picked.

Request
curl -X POST https://api.dpd-lt.com/v1/shipments \
  -H "Authorization: Bearer $KELVARA_API_KEY" \
  -H "Idempotency-Key: 8f2c1e04-ord-55219" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "ORD-55219",
    "service_level": "next_day",
    "depot_id": "dep_KAU01",
    "destination": {
      "name": "Rasa Jankauskienė",
      "street": "Verslo al. 12-4",
      "city": "Kaunas",
      "postal_code": "LT-44210",
      "country": "LT",
      "phone": "+37060000000"
    },
    "parcels": [
      { "weight_g": 1450, "size_class": "M" }
    ],
    "preferences": {
      "locker_allowed": true,
      "signature_required": false,
      "leave_with_neighbour": false
    }
  }'
201 Created
{
  "id": "shp_9F41CX22",
  "object": "shipment",
  "reference": "ORD-55219",
  "tracking_code": "KLV-8841207",
  "tracking_url": "https://track.dpd-lt.com/KLV-8841207",
  "status": "created",
  "service_level": "next_day",
  "destination": {
    "street": "Verslo al.",
    "house_number": "12",
    "unit": "4",
    "city": "Kaunas",
    "postal_code": "LT-44210",
    "country": "LT",
    "resolution": {
      "status": "exact",
      "confidence": 0.98
    }
  },
  "promised_window": {
    "from": "2026-07-15T09:00:00Z",
    "to":   "2026-07-15T17:00:00Z"
  },
  "created_at": "2026-07-14T08:11:02Z"
}

Errors

Errors use conventional HTTP status codes and a consistent body. The type field is stable and safe to branch on; the message is for humans and may change.

422 Unprocessable Entity
{
  "error": {
    "type": "address_unresolvable",
    "message": "Destination address could not be resolved with sufficient confidence.",
    "param": "destination.street",
    "request_id": "req_01HQ8M2K4Z7Y",
    "candidates": [
      { "street": "Verslo al.", "city": "Kaunas", "confidence": 0.61 },
      { "street": "Verslo g.",  "city": "Kaunas", "confidence": 0.44 }
    ]
  }
}

Always log request_id. It is the fastest way for our support team to find exactly what happened.

Webhooks

Events you can subscribe to

Register endpoint URLs in the console, choose the event types you care about, and Kelvara will POST each one as JSON. Delivery is at-least-once with exponential backoff over 24 hours, so make your handlers idempotent on the event id.

Kelvara webhook event catalogue
Event typeWhen it fires
Shipment lifecycle
shipment.createdA shipment has been accepted and given a tracking code.
shipment.assignedThe shipment has been placed on a committed route or reserved into a locker.
shipment.out_for_deliveryThe vehicle carrying the parcel has left the depot.
shipment.eta_updatedThe predicted arrival window has moved beyond the notification threshold.
shipment.deliveredDelivery completed and proof captured.
shipment.attempt_failedAn attempt did not succeed; the payload carries the reason code.
shipment.returnedThe parcel is on its way back to the depot or the sender.
shipment.cancelledCancelled by the sender or by an operator before dispatch.
Planning
plan.completedAn optimisation run has finished and routes are ready to review.
plan.failedA run could not produce a feasible plan; the payload lists the binding constraints.
route.committedA route has been released to a driver.
route.completedEvery stop on the route has reached a terminal state.
Lockers
locker.depositedA courier has placed a parcel into a compartment.
locker.collectedThe recipient has opened the compartment and taken the parcel.
locker.dwell_exceededA parcel has passed its configured collection grace period.
Exceptions
exception.openedAn exception has been raised against a shipment, route or cabinet.
exception.resolvedAn operator has closed the exception.
coldchain.excursionA monitored consignment has left its permitted temperature range.

Verifying signatures

Each delivery carries a Kelvara-Signature header containing a timestamp and an HMAC-SHA256 of timestamp + "." + raw_body, computed with your endpoint's signing secret. Compare using a constant-time function and reject anything with a timestamp older than five minutes.

Verify against the raw request body, before any JSON parsing or re-serialisation — reserialised JSON will not match.

Node.js · signature check
import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const [ts, sig] = header.split(",").map(p => p.split("=")[1]);

  // Reject replays outside a five-minute window.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(sig)
  );
}
Respond 2xx quickly and do your work asynchronously. Kelvara treats anything slower than 10 seconds as a failure and will retry, which is how duplicate processing starts.
Libraries

SDKs and tooling

Official clients are generated from the same OpenAPI document that backs the API, then hand-finished for ergonomics. All are MIT licensed and handle auth, retries with backoff, idempotency keys and cursor pagination for you.

TypeScript

Node 18+ and edge runtimes. Fully typed responses and discriminated unions for event payloads.

npm i @kelvara/sdk

Python

3.9+, sync and async clients, type stubs included.

pip install kelvara

PHP

8.1+, PSR-18 compatible, common for storefront integrations.

composer require kelvara/sdk

Java

17+, published to Maven Central. Used mostly for WMS and ERP integrations.

com.kelvara:sdk

OpenAPI document

Generate a client for any language from /v1/openapi.json. Versioned alongside the API.

Webhook replay tool

Replay any past event from the console against a local tunnel while you build a handler.

Postman collection

Every endpoint with sandbox examples, kept in step with each release.

Want sandbox credentials?

Sandbox access comes with every trial, including simulated vehicles you can watch complete a route in accelerated time. Tell us what you are integrating and we will get you a key.