Skip to main content

Creating orders

One order in your system becomes one order in Zoopit. This page covers creating it, retrying safely, changing it and cancelling it. The field-by-field schema is in the generated reference: Create an order.

Create an order​

POST https://orders-api.cloud.zoopit.no/v1/orders
Authorization: Bearer <token>
Content-Type: application/json

A typical shop delivery: picked up at the shop, delivered to a recipient within a time window, with your own order number as reference_id.

{
"owner": "<your project id>",
"reference_id": "100234",
"amounts": { "colli": 1 },
"attributes": { "erp_store": "majorstuen" },
"tasks": [
{
"type": "pickup",
"amounts": { "colli": 1 },
"places": [{
"duration": 2,
"time_windows": [{ "start": "2026-12-04T08:00:00+01:00", "end": "2026-12-04T11:00:00+01:00" }],
"location": {
"name": "Example Florist Majorstuen",
"phone_number": "+4722000000",
"address": {
"country_code": "no",
"street_address": "Bogstadveien 1",
"zip_code": "0355",
"city": "Oslo"
}
}
}]
},
{
"type": "delivery",
"amounts": { "colli": 1 },
"places": [{
"duration": 3,
"time_windows": [{ "start": "2026-12-04T10:00:00+01:00", "end": "2026-12-04T14:00:00+01:00" }],
"location": {
"name": "Kari Nordmann",
"phone_number": "+4790000000",
"address": {
"country_code": "no",
"street_address": "Kirkeveien 1",
"zip_code": "0268",
"city": "Oslo"
}
}
}]
}
],
"delivery": {
"oneOf": [{
"location": {
"name": "Kari Nordmann",
"phone_number": "+4790000000",
"address": {
"country_code": "no",
"street_address": "Kirkeveien 1",
"zip_code": "0268",
"city": "Oslo"
}
}
}]
}
}

It returns 201 with the order in resource. Store resource.id next to your own order number — it is the key for every later call and for webhooks.

What each part does:

FieldPurpose
ownerYour project id. Required.
reference_idYour order number. Shown in the dashboard, filterable, and returned in every status update and webhook.
tasksWhat gets planned. One pickup (the shop) and one delivery (the recipient). Planning and the driver app work from tasks.
tasks[].places[].durationTime spent at the stop, in minutes.
tasks[].places[].time_windowsWhen the stop may happen.
tasks[].amountsWhat is carried. Give the pickup and the delivery the same amounts.
delivery.oneOf[0].locationThe recipient again, for recipient SMS. SMS templates read the recipient from here, not from tasks.
attributesYour own key–value data.
Always send tasks

An order with only pickup / delivery and no tasks is created without error and shows up in lists, but planning never sees it: it produces routes with no stops. Send tasks. Until SMS templates can read the recipient from tasks, send the recipient in delivery.oneOf as well, as above.

Status on create​

New orders are RESERVED (committed to be delivered, ready for planning) unless you send "status": "CREATED". Use CREATED for an order that should exist in Zoopit but not be planned yet, and PATCH it to RESERVED when it is ready. See Statuses and reasons.

Addresses​

A location needs at least one of name, phone_number or email, and an address in one of two forms:

  • Structured — country_code, street_address, zip_code, city, optionally coordinates ({ "lat": …, "lon": … }). Preferred: it is geocoded more reliably.
  • Free text — { "country_code": "no", "search": "Kirkeveien 1, 0268 Oslo" }. The first geocoding match is used, so a wrong guess delivers to the wrong place without any error.

If you already hold reliable coordinates, send them and the address is not geocoded again.

location.comment.text attaches a note to that recipient's address, for example a door code. It is stored on the address, so it also appears on later deliveries to the same recipient at the same address. Put anything that only concerns this one order in attributes. Send a comment in tasks only, not again in delivery, or it is stored twice.

Retries and duplicates​

reference_id is not unique and there is no idempotency key. Sending the same order twice creates two orders.

If a create times out or fails with a 5xx, look before you retry:

curl -s -G 'https://orders-api.cloud.zoopit.no/v1/orders' \
-H 'Authorization: Bearer <token>' \
--data-urlencode 'where=[{"reference_id":{"=":"100234"}}]' \
--data-urlencode 'select=["id","reference_id","status","created_at"]' \
--data-urlencode 'limit=10'

If an order with your reference already exists, keep its id instead of creating another. If a duplicate slips through, cancel it with canceled.duplicate (below).

Read an order​

GET /v1/orders/:order_id

Returns the order and, in related, its tasks, locations, route, activities and order actions. To find an order by your own number, use the reference_id query above.

Update an order​

PATCH /v1/orders/:order_id
{
"modified_at": "2026-12-03T14:02:11.123456+00:00",
"attributes": { "erp_store": "majorstuen", "card_printed": true }
}
  • modified_at is required. Send the value from your latest read; send null if the order has never been modified. If the order has changed since — a driver completed a stop, a dispatcher moved it — the update is refused with 404 Not found or has been changed. Read the order again and retry.
  • Only the fields you send change. Fields you leave out keep their value.
  • Each field you send is replaced whole. attributes, tags, amounts, delivery and pickup are not merged: send {"attributes": {"a": 1}} and every other attribute key is gone. Read the order, merge your change into the existing object, and send the whole object.

To change a stop's address or time window, change the task: PATCH /v1/tasks/:id — see Update a task.

Do not set COMPLETED or FAILED yourself. Those are written as the driver works the route.

Cancel an order​

Cancelling keeps the order and its history, and it shows up in your status updates. If the order is already planned, cancelling also takes its stops off the route, so the driver no longer sees them.

POST /v1/orders/:order_id/cancel

{
"status_reason": "canceled.by_customer"
}

The response is the cancelled order, the same as for GET /v1/orders/:order_id. You don't need modified_at.

ReasonWhen
canceled.by_customerYour customer called it off
canceled.by_operatorYou or the carrier called it off
canceled.duplicateThe same delivery exists on another order
canceled.otherAnything else; requires status_description
{
"status_reason": "canceled.other",
"status_description": "Shop closed due to water damage"
}

A reason is required, and canceled.other without a description is refused with 400:

{ "request_id": "…", "message": "status_reason 'canceled.other' requires a non-empty status_description" }

What happens to a planned order​

  • Stops that are not done yet are taken off the route. The driver's app drops them on its next refresh, and the route's history in the dashboard records the cancellation with your reason.
  • Stops that are already done stay on the route. For example, if the goods have been picked up and the delivery is still to come, the pickup stays and the delivery is removed. The goods are still on the vehicle, so tell the dispatcher what should happen to them.
  • An order with no stops left on its route is taken off the route (route_id becomes null).

When cancelling is refused​

Cancelling returns 409 when the order:

  • is already CANCELED;
  • is COMPLETED;
  • has been delivered (the delivery stop is done), even if its status has not caught up yet.

404 means the order does not exist or is not in a project you can reach.

Cancelling with PATCH​

PATCH /v1/orders/:order_id with "status": "CANCELED" still works and follows the same rules, with the same 409s and the same stops removed. Prefer the cancel endpoint: it needs no modified_at, and it does not accept a cancellation without a reason.

A cancellation is final​

A cancelled order cannot be reopened. A PATCH that moves a CANCELED order to any other status returns 409. If the delivery is on again, create a new order.

Delete vs cancel​

DELETE /v1/orders/:order_id removes the order from every list and search. It cannot be read back, it does not appear in polling, and no webhook is sent. Use it only for orders created by mistake in testing. For anything a customer or dispatcher may ask about later, cancel.