Sapa Moto Partner API
Price a delivery, create orders, follow the courier and receive signed webhooks — from your own order system.
The Partner API connects your shop, restaurant or warehouse system to Sapa Moto couriers in Almaty. Everything a manager does in the cabinet can be done by your system: price a delivery, create and change orders, cancel them, follow the courier, fetch proof of delivery — and get a signed webhook on every status change.
How it works
- Quote —
POST /v1/quotesreturns the price, its breakdown and the ETA. The price holds for 10 minutes. - Order —
POST /v1/orderscreates the order (with the quote's price). You get atracking_urlfor the recipient and adelivery_codethe recipient tells the courier. - Ready —
POST /v1/orders/{id}/readywhen the order is packed (optional; otherwise the point's assembly norm applies). - Events — webhooks
order.status_changed,order.courier_assigned,order.eta_changed,order.delivered… - Proof —
GET /v1/orders/{id}/proof: photo, code check, geopoint and time.
Environments and keys
https://partner-sandbox.sapamoto.kz/v1— Sandbox — test keys sk_test_…, virtual courier, no moneyhttps://partner.sapamoto.kz/v1— Production — live keys sk_live_…
| Sandbox | Production | |
|---|---|---|
| Key | sk_test_… | sk_live_… |
| Available | right after your organization is created | after the integration checklist and the contract |
| Couriers | a virtual courier, 2–3 minutes per order | real couriers |
| Money | none — never in invoices or reports | invoices, deposit, cash on delivery |
| Paths and payloads | identical | identical |
Create keys in the cabinet, section «Интеграция». A key is shown once; store it in your secret manager. See Authentication.
First order in 5 minutes
You need a test key (sk_test_…). The sandbox accepts addresses with coordinates, so you do not need a pickup point
yet.
1. Price the delivery
curl https://partner-sandbox.sapamoto.kz/v1/quotes \
-H "Authorization: Bearer $SAPA_KEY" \
-H "Content-Type: application/json" \
-d '{
"pickup": {"address": "Алматы, Абая 10", "latitude": 43.2389, "longitude": 76.9286},
"dropoff": {"address": "Алматы, Жандосова 58", "latitude": 43.214, "longitude": 76.88}
}'const res = await fetch("https://partner-sandbox.sapamoto.kz/v1/quotes", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SAPA_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
pickup: { address: "Алматы, Абая 10", latitude: 43.2389, longitude: 76.9286 },
dropoff: { address: "Алматы, Жандосова 58", latitude: 43.214, longitude: 76.88 },
}),
});
const quote = await res.json();
console.log(quote.quote_id, quote.price.amount / 100, "₸", quote.eta_minutes, "min");import os, requests
quote = requests.post(
"https://partner-sandbox.sapamoto.kz/v1/quotes",
headers={"Authorization": f"Bearer {os.environ['SAPA_KEY']}"},
json={
"pickup": {"address": "Алматы, Абая 10", "latitude": 43.2389, "longitude": 76.9286},
"dropoff": {"address": "Алматы, Жандосова 58", "latitude": 43.214, "longitude": 76.88},
},
timeout=10,
).json()
print(quote["quote_id"], quote["price"]["amount"] / 100, "₸")<?php
$ch = curl_init('https://partner-sandbox.sapamoto.kz/v1/quotes');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('SAPA_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'pickup' => ['address' => 'Алматы, Абая 10', 'latitude' => 43.2389, 'longitude' => 76.9286],
'dropoff' => ['address' => 'Алматы, Жандосова 58', 'latitude' => 43.214, 'longitude' => 76.88],
]),
]);
$quote = json_decode(curl_exec($ch), true);
echo $quote['quote_id'], ' ', $quote['price']['amount'] / 100, " ₸\n";The answer is 201 with quote_id, price ({"amount": 145000, "currency": "KZT"} — 1 450 ₸ in tiyn),
breakdown, eta_minutes and valid_until.
2. Create the order
Pass the quote_id, your own order number as external_id and a fresh Idempotency-Key — so a retry after a
timeout never creates a second order.
curl https://partner-sandbox.sapamoto.kz/v1/orders \
-H "Authorization: Bearer $SAPA_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"external_id": "SHOP-100245",
"quote_id": "'"$QUOTE_ID"'",
"pickup": {"address": "Алматы, Абая 10", "latitude": 43.2389, "longitude": 76.9286},
"dropoff": {"address": "Алматы, Жандосова 58", "latitude": 43.214, "longitude": 76.88, "apartment": "41"},
"recipient": {"name": "Мадина", "phone": "+77011234567"},
"comment": "Позвонить за 10 минут"
}'import { randomUUID } from "node:crypto";
const order = await fetch("https://partner-sandbox.sapamoto.kz/v1/orders", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SAPA_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": randomUUID(), // store it with your order and reuse it on retries
},
body: JSON.stringify({
external_id: "SHOP-100245",
quote_id: quote.quote_id,
pickup: { address: "Алматы, Абая 10", latitude: 43.2389, longitude: 76.9286 },
dropoff: { address: "Алматы, Жандосова 58", latitude: 43.214, longitude: 76.88, apartment: "41" },
recipient: { name: "Мадина", phone: "+77011234567" },
}),
}).then((r) => r.json());
console.log(order.id, order.status, order.tracking_url, order.delivery_code);import uuid
order = requests.post(
"https://partner-sandbox.sapamoto.kz/v1/orders",
headers={
"Authorization": f"Bearer {os.environ['SAPA_KEY']}",
"Idempotency-Key": str(uuid.uuid4()), # store it with your order and reuse it on retries
},
json={
"external_id": "SHOP-100245",
"quote_id": quote["quote_id"],
"pickup": {"address": "Алматы, Абая 10", "latitude": 43.2389, "longitude": 76.9286},
"dropoff": {"address": "Алматы, Жандосова 58", "latitude": 43.214, "longitude": 76.88},
"recipient": {"name": "Мадина", "phone": "+77011234567"},
},
timeout=10,
).json()
print(order["id"], order["status"], order["tracking_url"])<?php
$ch = curl_init('https://partner-sandbox.sapamoto.kz/v1/orders');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SAPA_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)), // store it and reuse it on retries
],
CURLOPT_POSTFIELDS => json_encode([
'external_id' => 'SHOP-100245',
'quote_id' => $quote['quote_id'],
'pickup' => ['address' => 'Алматы, Абая 10', 'latitude' => 43.2389, 'longitude' => 76.9286],
'dropoff' => ['address' => 'Алматы, Жандосова 58', 'latitude' => 43.214, 'longitude' => 76.88],
'recipient' => ['name' => 'Мадина', 'phone' => '+77011234567'],
]),
]);
$order = json_decode(curl_exec($ch), true);
echo $order['id'], ' ', $order['status'], ' ', $order['tracking_url'], "\n";The order starts in created. In the sandbox a virtual courier picks it up within seconds and takes it to
delivered in about two minutes.
3. Follow it
curl https://partner-sandbox.sapamoto.kz/v1/orders/SHOP-100245 -H "Authorization: Bearer $SAPA_KEY"{id} accepts our id or your external_id. Better than polling: add a webhook endpoint in the cabinet (or with
POST /v1/webhooks) and receive signed events for every change.
4. Go live
When the sandbox flow works, pass the integration checklist in the cabinet — an order created, a webhook accepted
with a valid signature, a retried webhook handled, a cancellation, a failed delivery. Your account manager then
enables live keys. Switch the host to partner.sapamoto.kz and the key to sk_live_… — nothing else changes.
Conventions
- JSON, UTF-8,
snake_case. Ignore unknown fields — we add fields withinv1. - Money —
{"amount": <integer tiyn>, "currency": "KZT"}; 1 ₸ = 100 tiyn. Request fields that end in_tiynare plain integers in tiyn. - Time — ISO 8601 with an offset (
2026-10-24T15:00:00+05:00); responses are in UTC (Z). - Coordinates — WGS-84 decimal degrees.
- Errors —
application/problem+jsonwith a stablecode— see Errors. - Request id — every response has
X-Request-Id; send your own to correlate logs.