Base URL: https://business.kiloapp.org/api/v1
Auth on every request:
Authorization: Bearer sk_live_...
Types below use TypeScript-style notation:
string,number,booleanstring | null— may be JSONnull"a" | "b"— string enumISODateTime— ISO-8601 timestamp stringArray<T>— JSON array
---
Shared: error envelope
All failures use this shape (HTTP status varies):
{
"ok": false,
"error": "Human-readable message",
"code": "machine_code"
}
| Field | Type | Notes |
|---|---|---|
ok |
false |
Always false on errors |
error |
string |
Safe to show/log |
code |
string (optional but usual) |
Stable machine code |
fieldErrors |
Record<string, string[]> (optional) |
Present on validation failures |
Common auth / access codes
| HTTP | `code` | Meaning |
|---|---|---|
| 401 | missing_api_key |
No/invalid key format |
| 401 | invalid_api_key |
Unknown or revoked |
| 403 | missing_scope |
Key lacks required scope |
| 403 | test_keys_unsupported |
Legacy test key |
| 403 | billing_terminated |
Org cannot use API |
Validation error example
{
"ok": false,
"error": "Validation failed.",
"code": "validation_error",
"fieldErrors": {
"receiverPhone": ["Receiver phone is required."],
"packageImages": ["Package image URLs must use https://"]
}
}
---
Shared: Delivery object
Returned by create, get, list items, cancel, and webhook data.object.
type Delivery = {
id: string;
status: string; // e.g. "pending" | "matched" | "picked_up" | "in_transit" | "delivered" | "cancelled" | …
access_code: string | null;
title: string | null;
service_kind: "pickup_delivery" | "errand" | string | null;
request_source: "api" | "public_portal" | "dashboard" | string | null;
package: {
description: string | null;
size: "small" | "medium" | "large" | string | null;
is_fragile: boolean;
};
payment: {
delivery_fee: number | null;
delivery_payment_status: string | null;
currency_code: string | null; // e.g. "NGN"
customer_collection_method: "cash_on_delivery" | "online_checkout" | string | null;
is_pod_enabled: boolean;
pod_amount: number | null;
};
pickup: {
address: string | null;
latitude: number | null;
longitude: number | null;
warehouse_id: string | null; // UUID when warehouse mode
};
dropoff: {
address: string | null;
latitude: number | null;
longitude: number | null;
};
distance_km: number | null;
estimated_duration_minutes: number | null;
customer: {
sender_name: string | null;
sender_phone: string | null;
sender_email: string | null;
receiver_name: string | null;
receiver_phone: string | null;
};
rider_assigned: boolean;
created_at: ISODateTime;
updated_at: ISODateTime | null;
cancelled_at: ISODateTime | null;
cancellation_reason: string | null;
};
Example Delivery JSON
{
"id": "d_01HXYZ...",
"status": "pending",
"access_code": "ABC123",
"title": "Documents envelope",
"service_kind": "pickup_delivery",
"request_source": "api",
"package": {
"description": "Documents envelope",
"size": "small",
"is_fragile": false
},
"payment": {
"delivery_fee": 2500,
"delivery_payment_status": "pending",
"currency_code": "NGN",
"customer_collection_method": "cash_on_delivery",
"is_pod_enabled": false,
"pod_amount": null
},
"pickup": {
"address": "12 Admiralty Way, Lekki",
"latitude": 6.4474,
"longitude": 3.4721,
"warehouse_id": null
},
"dropoff": {
"address": "Victoria Island Office",
"latitude": 6.4281,
"longitude": 3.4219
},
"distance_km": 4.2,
"estimated_duration_minutes": 25,
"customer": {
"sender_name": "Ada Lovelace",
"sender_phone": "+2348012345678",
"sender_email": "ada@example.com",
"receiver_name": "Grace Hopper",
"receiver_phone": "+2348098765432"
},
"rider_assigned": false,
"created_at": "2026-08-09T18:00:00.000Z",
"updated_at": "2026-08-09T18:00:00.000Z",
"cancelled_at": null,
"cancellation_reason": null
}
---
`GET /capabilities`
Scope: deliveries:read
Success `200`
type CapabilitiesResponse = {
ok: true;
capabilities: {
organization: { id: string; name: string; slug: string };
plan: "basic" | "pro";
enabledServiceKinds: Array<"pickup_delivery" | "errand">;
defaultServiceKind: "pickup_delivery" | "errand";
allowWarehouseDropoff: boolean;
errand: {
enabledModes: Array<"at_place" | "link" | "photo">;
defaultMode: "at_place" | "link" | "photo";
allowUnknownShopLocation: boolean;
curatedLinks: Array<{ id: string; name: string; url: string }>;
};
paymentCollection: {
onlineCheckout: boolean;
cashOnDelivery: boolean;
};
currency: string;
paymentTiming: "before_dispatch" | "after_delivery" | "off_platform";
coverage: {
mode: "worldwide" | "selected_regions" | "radius_from_hub";
jobScopePolicy: string;
radiusKm: number | null;
regionCount: number;
};
pod: { enabled: boolean };
publicRequest: {
requireApproval: boolean;
autoDispatch: boolean;
pricingMode: "quote_on_review" | "fixed" | "starting_from" | "hidden";
pricingAmountNgn: number;
};
features: {
serviceLocations: boolean;
maxWarehouses: number | null; // 1 on basic, null = unlimited on pro
};
};
};
Errors
| HTTP | `code` |
|---|---|
| 503 | capabilities_failed |
| 404 | (org missing — rare with valid key) |
---
`GET /warehouses`
Scope: deliveries:read
Success `200`
type WarehousesResponse = {
ok: true;
warehouses: Array<{
id: string; // UUID
name: string;
address: string;
latitude: number | null;
longitude: number | null;
}>;
};
Errors
| HTTP | `code` |
|---|---|
| 503 | warehouses_failed |
---
`GET /service-locations`
Scope: deliveries:read
Empty array when the plan does not include service locations.
Success `200`
type ServiceLocationsResponse = {
ok: true;
serviceLocations: Array<{
id: string;
name: string;
address: string;
category: "hostel" | "building" | "library" | "gate" | "landmark" | "other";
coords: { lng: number; lat: number }; // use these exact values on create — do not re-geocode
countryCode: string | null;
stateCode: string | null;
countryName: string | null;
stateName: string | null;
unit: {
label: string;
mode: "none" | "free_text" | "list";
ranges: Array<{
prefix: string;
suffix: string;
start: number;
end: number;
pad: number;
}>;
values: string[];
allowCustom: boolean;
required: boolean;
};
}>;
};
---
`POST /deliveries`
Scope: deliveries:write
Request body (main fields)
type CreateDeliveryBody = {
serviceKind: "pickup_delivery" | "errand";
deliveryMode: "pickup" | "warehouse";
warehouseId?: string | null; // required UUID if deliveryMode = "warehouse"
senderName: string;
senderPhone: string;
senderEmail?: string | "";
pickupContactSameAsSender?: boolean;
pickupContactName?: string;
pickupContactPhone?: string;
receiverName?: string; // required for pickup_delivery
receiverPhone?: string; // required for pickup_delivery
pickupAddress: string;
pickupCoords: { lng: number; lat: number };
deliveryAddress: string;
deliveryCoords: { lng: number; lat: number };
packageDescription: string;
packageSize: "small" | "medium" | "large";
isFragile?: boolean;
isPODEnabled?: boolean;
podAmount?: number | null;
packageImages: string[]; // 0–6 https URLs; required min 1 for pickup_delivery OR errandMode "photo"
customerCollectionMethod: "cash_on_delivery" | "online_checkout";
idempotencyKey?: string; // 8–120 chars
// errand-only extras when serviceKind = "errand":
errandMode?: "at_place" | "link" | "photo";
errandLinks?: Array<{ url: string; note: string; curatedId?: string | null }>;
errandBudgetNgn?: string;
errandSubstituteOk?: boolean;
shopPhone?: string;
pickupLocationUnknown?: boolean;
distanceKm?: number;
estimatedDurationMinutes?: number;
};
Success `201`
type CreateDeliveryResponse = {
ok: true;
delivery: Delivery;
};
Errors
| HTTP | `code` | When |
|---|---|---|
| 400 | validation_error |
Bad/missing fields (fieldErrors) |
| 400 | image_host_rejected |
Host not allowlisted / SSRF / download fail |
| 403 | service_disabled |
serviceKind not enabled for org |
| 403 | (various) | Coverage / money / trust rejections via create path |
| 503 | create_failed / auth lookup |
Upstream failure |
---
`GET /deliveries/{deliveryId}`
Scope: deliveries:read
Success `200`
type GetDeliveryResponse = {
ok: true;
delivery: Delivery;
};
Errors
| HTTP | `code` |
|---|---|
| 400 | validation_error |
| 404 | not_found |
| 500 | get_failed |
---
`GET /deliveries`
Scope: deliveries:read
Query
| Param | Type | Default |
|---|---|---|
limit |
number (1–50) |
20 |
status |
string (optional) |
— |
cursor |
string UUID (optional) |
— |
Success `200`
type ListDeliveriesResponse = {
ok: true;
deliveries: Delivery[];
next_cursor: string | null;
};
Errors
| HTTP | `code` |
|---|---|
| 400 | validation_error |
| 500 | (list failure message) |
---
`POST /deliveries/{deliveryId}/cancel`
Scope: deliveries:write
Request
type CancelBody = {
reason: string; // 3–500 chars
};
Success `200`
type CancelResponse = {
ok: true;
delivery: Delivery; // status "cancelled" (or minimal { id, status } if reload fails)
};
Errors
| HTTP | `code` |
|---|---|
| 400 | validation_error |
| 400 | cancel_not_allowed |
| 404 | not_found |
| 500 | cancel_failed |
---
Webhook POST (to your URL)
Kilo → your HTTPS endpoint.
Headers
| Header | Type |
|---|---|
Content-Type |
application/json |
Kilo-Event |
string event name |
Kilo-Signature |
string t=<unix>,v1=<hex> |
Body
type WebhookEvent = {
id: string;
type:
| "delivery.created"
| "delivery.updated"
| "delivery.status_changed"
| "delivery.cancelled"
| "delivery.completed";
created: number; // unix seconds
data: {
object: Delivery;
previous_status: string | null;
};
};
Your response
Return any HTTP 2xx quickly after verifying the signature. Non-2xx may be retried.