Kanal Partner API
Server-to-server REST API for partner storefronts to provision Kanal customer accounts. Integrate with your signup flow so your customers land in Kanal instantly — no manual steps.
Overview
Current version
v1
Protocol
HTTPS / REST
Auth scheme
Bearer Token
The Kanal Partner API lets trusted integrators create customer accounts on behalf of a courier company. Every API key is scoped to a single Kanal tenant. Accounts created via the API receive a branded welcome email with their temporary password, and immediately appear in the courier's customer list with a unique customer number.
Base URL
https://kanal.cyrusfieldtech.com/apiAll Partner API paths are relative to this base. For example, the customer creation endpoint is at /api/partner/v1/customers.
Authentication
All Partner API requests require a Bearer token in the Authorization header. Tokens are issued by your Kanal super-admin from the Companies page and follow the format kanal_pk_XXXXXXXXXX.YYYYYY.
Authorization: Bearer kanal_pk_YOUR_KEY_HERERate Limits
Partner API requests share the per-tenant rate limit tier. Limits apply per rolling minute window:
| Plan | Requests / minute |
|---|---|
| Starter | 60 |
| Pro | 300 |
| Scale | 1,000 |
Exceeding the rate limit returns 429 Too Many Requests.
Create Customer
/partner/v1/customersProvisions a new customer account under the tenant associated with your API key. The new customer receives a branded welcome email with their temporary password and a link to log in. A unique customer number is auto-assigned and returned for use in shipment bookings.
Request body
| Field | Type | Description |
|---|---|---|
emailrequired | string (email) | Valid email address. Must be unique within this courier's tenant. Used as the customer's login. |
namerequired | string | Customer's full name. Min 1 character. |
countryrequired | string (ISO-2) | ISO 3166-1 alpha-2 country code (e.g. JM, US). Defaults to JM. |
phone | string | Customer's phone number including country code (e.g. +18765551234). |
address | string | Customer's shipping address. Stored as the default address on their recipient profile. |
trn | string | Jamaica Tax Registration Number — exactly 9 digits (dashes allowed: 123-456-789). Only accepted when country=JM. |
Example request
curl -X POST https://kanal.cyrusfieldtech.com/api/partner/v1/customers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE" \
-d '{
"email": "alice@example.com",
"name": "Alice Brown",
"phone": "+18765551234",
"country": "JM",
"address": "12 Hope Road, Kingston 6, Jamaica",
"trn": "123456789"
}'Response — 200 OK
{
"ok": true,
"customer_number": "SW460329",
"user_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "alice@example.com",
"already_existed": false
}| Field | Description |
|---|---|
ok | Always true on success. |
customer_number | Stable, auto-assigned identifier for this customer within the courier (e.g. SW460329). Use this when booking shipments. |
user_id | Internal UUID for the newly created user. |
email | Lowercased, normalised email as stored. |
already_existed | Always false for a successful creation. Reserved for future idempotency behavior. |
Get Customer Profile
/partner/v1/customers/{identifier}Retrieve a customer's account and recipient profile. The identifier path segment accepts either an email address or a customer number (returned from the create endpoint).
Lookup by email
curl https://kanal.cyrusfieldtech.com/api/partner/v1/customers/alice%40example.com \
-H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE"Lookup by customer number
curl https://kanal.cyrusfieldtech.com/api/partner/v1/customers/SW460329 \
-H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE"Response — 200 OK
{
"user_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "alice@example.com",
"name": "Alice Brown",
"phone": "+18765551234",
"customer_number": "SW460329",
"country": "JM",
"address": "12 Hope Road, Kingston 6, Jamaica",
"trn": "123456789",
"account_status": "active",
"email_verified": true,
"created_at": "2026-01-15T14:30:00Z"
}| Field | Description |
|---|---|
user_id | UUID of the customer's Kanal login account. null if the customer has packages but no login. |
email | Customer's email address (lowercased). |
name | Customer's display name. |
phone | Phone number, or null if not provided. |
customer_number | Stable alphanumeric identifier (e.g. SW460329). Use this in shipment bookings. |
country | ISO-2 country code recorded at signup. |
address | Default shipping address, or null. |
trn | Jamaica TRN (9 digits), or null. Returned unmasked to the issuing partner. |
account_status | One of active, pending_verification, blocked, or recipient_only (shipment record exists but no login account). |
email_verified | true once the customer has clicked the welcome email link. |
created_at | ISO 8601 timestamp of account creation. |
List Customer Packages
/partner/v1/customers/{identifier}/packagesReturns a paginated list of all shipments associated with the customer, ordered newest first. Accepts the same email-or-customer-number identifier as the profile endpoint.
Query parameters
| Param | Type | Description |
|---|---|---|
limit | integer | Page size — 1 to 100. Default: 20. |
offset | integer | Number of records to skip. Default: 0. |
status | string | Filter to a specific status: pending · in_transit · ready_for_pickup · delivered · cancelled · return_to_sender |
Example — all packages by customer number
curl "https://kanal.cyrusfieldtech.com/api/partner/v1/customers/SW460329/packages?limit=10&offset=0" \
-H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE"Example — delivered packages by email
curl "https://kanal.cyrusfieldtech.com/api/partner/v1/customers/alice%40example.com/packages?status=delivered&limit=5" \
-H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE"Response — 200 OK
{
"total": 2,
"limit": 10,
"offset": 0,
"items": [
{
"package_id": "a1b2c3d4-0001-0000-0000-000000000001",
"tracking_number": "KN1029384756",
"status": "delivered",
"shipment_method": "air",
"description": "Electronics",
"weight_kg": 1.2,
"sender_name": "Cyber Store Ltd",
"sender_address": "11420 Fortune Cir Unit 34, Wellington FL 33414",
"created_at": "2026-01-10T09:00:00Z",
"updated_at": "2026-01-18T14:22:00Z",
"delivered_at": "2026-01-18T14:22:00Z"
},
{
"package_id": "a1b2c3d4-0002-0000-0000-000000000002",
"tracking_number": "KN9876543210",
"status": "in_transit",
"shipment_method": "sea",
"description": "Clothing",
"weight_kg": 3.5,
"sender_name": "Fashion Depot",
"sender_address": "200 Fashion Ave, New York NY 10001",
"created_at": "2026-01-20T11:00:00Z",
"updated_at": "2026-01-22T08:00:00Z",
"delivered_at": null
}
]
}| Field | Description |
|---|---|
total | Total count of matching packages (before pagination). |
limit / offset | Echo of the pagination params supplied in the request. |
items[].package_id | Internal UUID of the package. |
items[].tracking_number | KN-prefixed tracking code (e.g. KN1029384756). Use for public tracking at /c/{slug}/track/{number}. |
items[].status | Current delivery status. |
items[].shipment_method | air · sea · ground. |
items[].weight_kg | Package weight in kilograms. |
items[].sender_name | Name of the US/origin sender or store. |
items[].delivered_at | ISO 8601 delivery timestamp, or null if not yet delivered. |
Get Shipping Rates
/partner/v1/ratesReturns the courier's live shipping rate table authenticated with your partner key — no customer JWT required. Use this to display up-to-date rates on your storefront. The response always reflects the latest rates as configured by the courier admin.
Example request
curl https://kanal.cyrusfieldtech.com/api/partner/v1/rates \
-H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE"Response — 200 OK
{
"currency": "JMD",
"per_lb_rate": 600,
"rate_table": [
{ "lbs": 1, "price": 600 },
{ "lbs": 2, "price": 1000 },
{ "lbs": 3, "price": 1400 },
{ "lbs": 4, "price": 1750 },
{ "lbs": 5, "price": 2100 },
...
{ "lbs": 100, "price": 35350 }
]
}| Field | Description |
|---|---|
currency | ISO 4217 currency code for all prices in this response (e.g. JMD, USD). |
per_lb_rate | Base rate for 1 lb — convenience field matching rate_table[0].price. |
rate_table | Ordered array of weight tiers, each with lbs (integer) and price (number in the stated currency). Use lbs as an exact lookup key for the package weight. |
const { rate_table, currency } = await fetch('/api/partner/v1/rates', {
headers: { Authorization: `Bearer ${KANAL_PARTNER_KEY}` }
}).then(r => r.json());
const tier = rate_table.find(t => t.lbs === packageWeightLbs);
const price = tier?.price ?? null; // null = weight not in tableError Reference
All errors return JSON with a detail field. Standard HTTP status codes apply.
| Code | Status | Meaning |
|---|---|---|
| 400 | Bad Request | Validation failed — e.g., TRN provided without country=JM, or country code not exactly 2 letters. |
| 401 | Unauthorized | Missing, malformed, or revoked API key. |
| 402 | Payment Required | The owning courier's subscription invoice is overdue. Resolve outstanding invoices to re-enable API access. |
| 409 | Conflict | A customer with this email already exists in the courier's tenant. |
| 422 | Unprocessable Entity | Request body failed Pydantic schema validation (invalid email format, missing required field, etc.). |
| 429 | Too Many Requests | Per-tenant rate limit exceeded. Retry after the rolling window resets (~60 seconds). |
| 500 | Internal Server Error | Unexpected server error. Contact support if this persists. |
409 — Duplicate email
{
"detail": "Email already registered for this tenant"
}401 — Invalid key
{
"detail": "Invalid or revoked partner API key"
}402 — Locked tenant
{
"detail": "Owning tenant account is locked — resolve outstanding invoices to re-enable partner API access"
}422 — Validation error
{
"detail": [
{
"loc": ["body", "email"],
"msg": "value is not a valid email address",
"type": "value_error.email"
}
]
}Getting an API Key
Contact your Kanal admin
API keys are issued per courier tenant by the Kanal super-admin. Email your account manager or contact us at dev@cyrusfieldtech.com to request access for your integration.
Key is minted on the Companies page
The super-admin navigates to Companies → selects your courier → opens the Partner API Keys panel → clicks 'Generate key'. The plaintext key is shown exactly once at creation time.
Store it securely
Copy the key immediately and store it in your server environment variables (e.g., KANAL_API_KEY). It is never shown again — if lost, revoke and generate a new one.
Start creating customers
Use the key in the Authorization header as shown in the examples above. Each request creates a Kanal customer account scoped to your courier's tenant.
Questions or custom integrations?
Reach out at dev@cyrusfieldtech.com — we can help with server-side auth flows, webhooks, and white-label integrations.