Quickstart
You will make a test token, create a shipment, receive its webhooks and walk it to delivered, without a driver or real money. Every step comes in curl, Node (18 or later) and Python.
1. Make a test token
Open Settings → Developers → API tokens in the dashboard (merchants: Portal → Developers) and choose Create token:
- Mode: Test. Test tokens only see and create sandbox shipments.
- Scopes:
shipments:write,rates:read,webhooks:write.
Copy the token (atk_test_…). It is shown once. Keep it in an environment variable, then set up
a small helper that sends it as a bearer token and adds an Idempotency-Key to every write:
export ANTIVIO_TOKEN=atk_test_...
export ANTIVIO=https://api.antivio.app
# Every request sends the token as a bearer token. You need curl, jq and uuidgen.
curl -s "$ANTIVIO/v1/shipments?limit=5" -H "Authorization: Bearer $ANTIVIO_TOKEN"// quickstart.mjs: run with ANTIVIO_TOKEN=atk_test_... node quickstart.mjs
const ANTIVIO = "https://api.antivio.app";
async function antivio(method, path, body, idempotencyKey = crypto.randomUUID()) {
const res = await fetch(ANTIVIO + path, {
method,
headers: {
Authorization: `Bearer ${process.env.ANTIVIO_TOKEN}`,
...(method === "GET" ? {} : { "Idempotency-Key": idempotencyKey }),
...(body ? { "Content-Type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.status} ${data.code}: ${data.detail}`);
return data;
}
console.log(await antivio("GET", "/v1/shipments?limit=5"));# quickstart.py: pip install requests, then ANTIVIO_TOKEN=atk_test_... python quickstart.py
import os
import uuid
import requests
ANTIVIO = "https://api.antivio.app"
http = requests.Session()
http.headers["Authorization"] = f"Bearer {os.environ['ANTIVIO_TOKEN']}"
def antivio(method, path, body=None, idempotency_key=None):
headers = {} if method == "GET" else {"Idempotency-Key": idempotency_key or str(uuid.uuid4())}
res = http.request(method, ANTIVIO + path, json=body, headers=headers)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['status']} {data['code']}: {data.get('detail')}")
return data
print(antivio("GET", "/v1/shipments?limit=5"))The Node and Python steps below add to the same file and reuse antivio().
2. Receive webhooks
Any public HTTPS URL works. For a first try use a request inspector (for example
https://webhook.site) or expose your local server with a tunnel. Register it, then send it a test
message:
# Your receiver: the inspector's or the tunnel's HTTPS URL.
export WEBHOOK_URL=https://...
ENDPOINT=$(curl -s "$ANTIVIO/v1/webhook-endpoints" \
-H "Authorization: Bearer $ANTIVIO_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"url": "'"$WEBHOOK_URL"'",
"event_types": ["shipment.created", "shipment.status_changed", "shipment.delivered"]}')
echo "$ENDPOINT" # keep .secret
ENDPOINT_ID=$(echo "$ENDPOINT" | jq -r .endpoint.id)
curl -s "$ANTIVIO/v1/webhook-endpoints/$ENDPOINT_ID/test" \
-H "Authorization: Bearer $ANTIVIO_TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" -d '{"event_type": "shipment.delivered"}'const WEBHOOK_URL = "https://..."; // the inspector's or the tunnel's HTTPS URL
const { endpoint, secret } = await antivio("POST", "/v1/webhook-endpoints", {
url: WEBHOOK_URL,
event_types: ["shipment.created", "shipment.status_changed", "shipment.delivered"],
});
console.log("Signing secret (keep it):", secret);
console.log(
await antivio("POST", `/v1/webhook-endpoints/${endpoint.id}/test`, {
event_type: "shipment.delivered",
}),
);WEBHOOK_URL = "https://..." # the inspector's or the tunnel's HTTPS URL
created = antivio("POST", "/v1/webhook-endpoints", {
"url": WEBHOOK_URL,
"event_types": ["shipment.created", "shipment.status_changed", "shipment.delivered"],
})
print("Signing secret (keep it):", created["secret"])
print(antivio("POST", f"/v1/webhook-endpoints/{created['endpoint']['id']}/test",
{"event_type": "shipment.delivered"}))The first answer has secret (whsec_…): keep it to
verify signatures. The test answer shows what your server
replied.
3. Create a shipment
Shipments go between two locations of your address book. Create both, then a draft shipment, then confirm it: confirming prices it and gives it a tracking number.
A merchant's token (made in the customer portal) always acts for that merchant's account. A
company token (made under Settings) acts for the whole company: add
"customer_account_id": "…" to say which customer each location and shipment belongs to.
# Where the parcel is picked up, and where it goes.
ORIGIN=$(curl -s "$ANTIVIO/v1/locations" -H "Authorization: Bearer $ANTIVIO_TOKEN" \
-H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \
-d '{"label": "Warehouse", "location": {"lat": 18.4607, "lng": -69.9177}}' | jq -r .id)
DESTINATION=$(curl -s "$ANTIVIO/v1/locations" -H "Authorization: Bearer $ANTIVIO_TOKEN" \
-H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \
-d '{"label": "Ana Pérez", "location": {"lat": 18.4719, "lng": -69.8923},
"address_text": "Calle El Conde 101", "contact_phone": "+18095550199"}' | jq -r .id)
# A draft, then confirm it.
SHIPMENT=$(curl -s "$ANTIVIO/v1/shipments" -H "Authorization: Bearer $ANTIVIO_TOKEN" \
-H "Content-Type: application/json" -H "Idempotency-Key: order-1001" \
-d '{"origin_location_id": "'$ORIGIN'", "destination_location_id": "'$DESTINATION'",
"recipient_name": "Ana Pérez", "recipient_phone": "+18095550199",
"external_ref": "ORDER-1001",
"parcels": [{"weight_g": 1000, "length_mm": 300, "width_mm": 200, "height_mm": 100}]}' | jq -r .id)
curl -s -X POST "$ANTIVIO/v1/shipments/$SHIPMENT/confirm" \
-H "Authorization: Bearer $ANTIVIO_TOKEN" -H "Idempotency-Key: confirm-1001"// Where the parcel is picked up, and where it goes.
const origin = await antivio("POST", "/v1/locations", {
label: "Warehouse",
location: { lat: 18.4607, lng: -69.9177 },
});
const destination = await antivio("POST", "/v1/locations", {
label: "Ana Pérez",
location: { lat: 18.4719, lng: -69.8923 },
address_text: "Calle El Conde 101",
contact_phone: "+18095550199",
});
// A draft, then confirm it. Your order number makes a good Idempotency-Key.
const shipment = await antivio(
"POST",
"/v1/shipments",
{
origin_location_id: origin.id,
destination_location_id: destination.id,
recipient_name: "Ana Pérez",
recipient_phone: "+18095550199",
external_ref: "ORDER-1001",
parcels: [{ weight_g: 1000, length_mm: 300, width_mm: 200, height_mm: 100 }],
},
"order-1001",
);
console.log(await antivio("POST", `/v1/shipments/${shipment.id}/confirm`, null, "confirm-1001"));# Where the parcel is picked up, and where it goes.
origin = antivio("POST", "/v1/locations", {
"label": "Warehouse",
"location": {"lat": 18.4607, "lng": -69.9177},
})
destination = antivio("POST", "/v1/locations", {
"label": "Ana Pérez",
"location": {"lat": 18.4719, "lng": -69.8923},
"address_text": "Calle El Conde 101",
"contact_phone": "+18095550199",
})
# A draft, then confirm it. Your order number makes a good Idempotency-Key.
shipment = antivio("POST", "/v1/shipments", {
"origin_location_id": origin["id"],
"destination_location_id": destination["id"],
"recipient_name": "Ana Pérez",
"recipient_phone": "+18095550199",
"external_ref": "ORDER-1001",
"parcels": [{"weight_g": 1000, "length_mm": 300, "width_mm": 200, "height_mm": 100}],
}, idempotency_key="order-1001")
print(antivio("POST", f"/v1/shipments/{shipment['id']}/confirm", idempotency_key="confirm-1001"))Your endpoint gets shipment.created, then shipment.status_changed (draft → ready).
Tip: use your order number as the Idempotency-Key. If the request times out, send it again with
the same key: you get the first answer instead of a second shipment.
4. Walk it to delivered
In test mode the simulator moves a sandbox shipment one step at a time through the same rules a real delivery follows:
for to in pickup_assigned picked_up out_for_delivery delivered; do
curl -s "$ANTIVIO/v1/sandbox/shipments/$SHIPMENT/advance" \
-H "Authorization: Bearer $ANTIVIO_TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" -d '{"to": "'$to'"}'
donefor (const to of ["pickup_assigned", "picked_up", "out_for_delivery", "delivered"]) {
const moved = await antivio("POST", `/v1/sandbox/shipments/${shipment.id}/advance`, { to });
console.log(moved.status);
}for to in ["pickup_assigned", "picked_up", "out_for_delivery", "delivered"]:
moved = antivio("POST", f"/v1/sandbox/shipments/{shipment['id']}/advance", {"to": to})
print(moved["status"])Each step sends shipment.status_changed, and the last one shipment.delivered.
5. Go live
Make a live token with the scopes you need and a live endpoint pointing at your production
URL. Live and test tokens use the same routes and messages; test endpoints only receive sandbox
events and live endpoints only live ones ("livemode").