Kilo Docs

Docs / Reference/ API reference

API reference

Updated Aug 9, 2026

Base URL: https://business.kiloapp.org/api/v1

Auth on every request:

Authorization: Bearer sk_live_...

Types below use TypeScript-style notation:

  • string, number, boolean
  • string | null — may be JSON null
  • "a" | "b" — string enum
  • ISODateTime — ISO-8601 timestamp string
  • Array<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.