Partner API for shipping companies
Submit sourcing requests on behalf of your customers, receive priced quotations, upload proof of payment, and stream order-status updates through signed webhooks, all over HTTPS with a single API key.
Overview
The Partner API lets a third-party shipping company or sourcing partner submit orders into our platform programmatically. Each order flows through the same fulfilment pipeline as one created from our web UI, and the partner receives webhook callbacks at every meaningful state change.
https://kuaisourcing.com/api-backAll endpoints below are relative to this base.Authentication
Every request must include your API key in the x-api-key header. The key identifies which partner account is calling, keep it secret.
POST /api/partner/sourcing HTTP/1.1
Host: kuaisourcing.com
Content-Type: application/json
x-api-key: ks_live_yourkeyhere1234567890abcdefNever embed the key in client-side code or commit it to git. Treat it like a password. If exposed, ask your account manager to rotate it, the old key is revoked immediately.
Order lifecycle
An order moves through these states. The order.status_changed webhook fires on every transition.
Terminal off-path statuses: Cancelled, Refunded, Order Rejected, OutOfStock.
Submit a sourcing request
One request can include multiple destinations. Each destination must declare its shipping service, either from our catalog or self-arranged.
{
"productName": "Bluetooth Earbuds Pro",
"productImageUrl": "https://yourcdn.com/img.jpg",
"productURL": "https://supplier-page.example/product/123",
"category": "Electronics",
"targetMarket": "Africa",
"additionalNotes": "Need OEM packaging",
"airFreight": true,
"seaFreight": false,
"byTrain": false,
"expressDelivery": false,
"normalDelivery": true,
"domesticDelivery": "slow",
"shippingResponsibility": "kuai",
"destinations": [
{
"country": "Gabon",
"city": "Libreville",
"address": "1 Example Street",
"quantity": 50,
"variants": "[{\"color\":\"Black\",\"quantity\":30},{\"color\":\"White\",\"quantity\":20}]",
"service": "DHL",
"recipientName": "Jane Doe",
"contactNumber": "+241 00 00 00 00"
},
{
"country": "Tunisia",
"city": "Tunis",
"quantity": 30,
"service": "Other ...",
"customService": "Local forwarder name",
"recipientName": "John Doe",
"contactNumber": "+216 00 000 000",
"isCompanyService": false
}
]
}Top-level fields
| Field | Required | Notes |
|---|---|---|
| productName | yes | Short product label |
| targetMarket | yes | e.g. "Africa" |
| destinations | yes | At least one entry. Each entry must have country + quantity. |
| airFreight / seaFreight / byTrain | one must be true | If airFreight is true, also set expressDelivery or normalDelivery. |
| domesticDelivery | optional | "fast" or "slow" (inside China) |
| shippingResponsibility | optional | "kuai" / "service" / "self" |
| productImageUrl | optional | Must resolve. Upload via the uploads endpoint first to be safe. |
Response · 201 Created
{
"success": true,
"data": {
"orderId": "9f5e2c3a-...",
"customOrderId": "KS0000",
"message": "Sourcing request submitted successfully"
}
}Use orderId (UUID) for all subsequent calls. customOrderId (KS0001, KS0002, …) is the same sequential id shown in our internal dashboard.
List your orders
| Param | Default | Notes |
|---|---|---|
| status | (any) | Filter by exact status (see lifecycle section) |
| details | true | false for a slimmer payload (no quotations / real images / destinations) |
| page | 0 | Zero-indexed |
| size | 100 | Max 100 |
Returns a Spring Page of orders (see Get an order for shape).
Get an order
Either form returns the same payload.
{
"success": true,
"data": {
"id": "9f5e2c3a-...",
"customOrderId": "KS0000",
"productName": "Bluetooth Earbuds Pro",
"status": "Unpaid",
"formCreationDate": "2026-05-10T08:14:32",
"lastUpdated": "2026-05-13T11:02:08",
"realImages": [
{ "id": "...", "image": "https://files.example.com/uploads/real-1.jpg", "addedAt": "2026-05-13T10:55:00" },
{ "id": "...", "image": "https://files.example.com/uploads/demo.mp4", "addedAt": "2026-05-13T11:00:00" }
],
"destinations": [
{
"id": "d4...",
"country": "Gabon",
"city": "Libreville",
"quantity": 50,
"service": { "name": "DHL", "recipientName": "Jane Doe" },
"quotation": {
"totalCost": 1840.00,
"unitPrice": 35.00,
"internationalShippingCost": 90.00,
"deliveryCostInChina": 70.00,
"extraFees": 0.00
}
}
]
}
}Modify an order
Same body shape as submit. Pass each destination's id back to update it in place (keeps the linked quotation attached). Destinations not in the payload are removed; new ones (no id) are created.
Upload a file
Multipart form. Returns a permanent S3 URL you can reuse as productImageUrl, proof-of-payment url, etc.
curl -X POST https://kuaisourcing.com/api-back/api/partner/uploads \
-H "x-api-key: $KEY" \
-F "file=@receipt.pdf" \
-F "purpose=proof-of-payment"
# Response
{
"success": true,
"data": {
"url": "https://files.example.com/uploads/receipt.pdf",
"size": 27695,
"contentType": "application/pdf"
}
}Accepted: jpg, jpeg, png, webp, pdf, mp4, mov, webm. Max 25 MB.
Submit proof of payment
Attach a receipt to an existing order. Upload the file via the uploads endpoint first and pass the returned URL here.
{
"country": "Gabon",
"url": "https://files.example.com/uploads/receipt.pdf",
"paymentNote": "Bank transfer EUR to USD, reference KS0000"
}On success the order's status auto-moves Unpaid → Payment pending and an order.status_changed webhook fires.
List available services
Deduplicated by name (case-insensitive). One service can operate in many countries, each row shows the coverage list.
{
"success": true,
"data": [
{ "id": "00000000-0000-4000-8000-000000000001", "name": "Example Freight Co", "countries": ["Morocco"] },
{ "id": "00000000-0000-4000-8000-000000000002", "name": "Sample Logistics", "countries": ["Senegal", "Madagascar"] },
{ "id": "00000000-0000-4000-8000-000000000003", "name": "Demo Shipping Line", "countries": ["Spain"] }
]
}Service selection rules
| Field passed | Behaviour |
|---|---|
| serviceId | Preferred, unambiguous, links to that catalog row |
| service | Name match, case-insensitive. "DHL" = "dhl" = "Dhl". |
| service: "Other ..." | See self-arranged shipping, extra fields required. |
| Anything else | 400 with "Service 'X' is not in our catalog…" |
Self-arranged shipping
When you handle the shipping yourself, your own courier, a local forwarder, or personal pickup, set service: "Other ..." on the destination and provide the same fields a normal user would fill in our UI.
| Field | Required | Purpose |
|---|---|---|
| customService | yes | Free-text forwarder / courier name (e.g. "My local Tunis forwarder") |
| recipientName | yes | Person receiving at destination |
| contactNumber | yes | Recipient phone, used by the local courier |
| isCompanyService | optional | Default false. Set true for registered company forwarders. |
| address, city | optional | Inherited from the destination, also used as the shipping address. |
These rows are flagged userSubmitted=true server-side, so they never appear in the shared services catalog.
Missing any required field returns 400 with a pointer to this section.
Webhooks
Configure a webhook URL when your API key is provisioned. We deliver events asynchronously with HMAC-SHA256 signatures and retry on failure.
| Event | Fired when… |
|---|---|
| quotation.created | Admin issues a priced quotation on one of your destinations |
| quotation.updated | An existing quotation is edited |
| order.status_changed | Status moves (e.g. Unpaid → Paid → In Production → Shipped) |
| realimages.added | Photos / videos of the actual product are uploaded |
If a notificationEmail is also configured on your key, the same events trigger a parallel email digest. Either channel is optional.
Best-effort. We retry on non-2xx responses with exponential backoff (1s, 2s, 4s, 8s, 16s). Respond 2xx within 10 seconds or we treat the attempt as a failure.
Error format
All errors share the same envelope. The HTTP status is meaningful, your client should branch on it before reading the body.
HTTP 400 Bad Request
{
"success": false,
"data": null,
"error": "destinations: At least one destination is required",
"timestamp": "2026-05-18T11:42:03.581"
}| Code | Meaning |
|---|---|
400 | Validation failed, see the error field for the specific issue. Don't retry without fixing. |
401 | Missing or invalid x-api-key |
403 | Key revoked or quota exceeded |
404 | Order or resource not found (or not owned by your key) |
409 | Order in a non-editable status, can't modify |
429 | Rate limit, back off and retry |
500 | Server error, safe to retry with exponential backoff |
Endpoint cheatsheet
| Method · Path | What it does |
|---|---|
| POST /api/partner/sourcing | Create sourcing request |
| GET /api/partner/sourcing | List your orders (paginated, filterable) |
| GET /api/partner/sourcing/{orderId} | Fetch one order (full detail) |
| GET /api/partner/sourcing/by-order-id/{customOrderId} | Same, by KS code |
| PUT /api/partner/sourcing/{orderId} | Modify (only Processing / Unpaid) |
| POST /api/partner/uploads | Upload file → permanent S3 URL |
| POST /api/partner/sourcing/{orderId}/proof-of-payment | Attach receipt; status moves Unpaid → Payment pending |
| GET /api/partner/services | List the shipping-services catalog |