Back|
KanalPartner APIv1
Partner APILive

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

base url
https://kanal.cyrusfieldtech.com/api

All 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.

http
Authorization: Bearer kanal_pk_YOUR_KEY_HERE
Keep your key secret. Treat it like a password — never expose it in client-side code or public repos. Revoke and rotate keys immediately if they are compromised. A super-admin can revoke any key from the Kanal dashboard.

Rate Limits

Partner API requests share the per-tenant rate limit tier. Limits apply per rolling minute window:

PlanRequests / minute
Starter60
Pro300
Scale1,000

Exceeding the rate limit returns 429 Too Many Requests.

Create Customer

POST/partner/v1/customers

Provisions 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

FieldTypeDescription
emailrequired
string (email)Valid email address. Must be unique within this courier's tenant. Used as the customer's login.
namerequired
stringCustomer'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
stringCustomer's phone number including country code (e.g. +18765551234).
address
stringCustomer's shipping address. Stored as the default address on their recipient profile.
trn
stringJamaica Tax Registration Number — exactly 9 digits (dashes allowed: 123-456-789). Only accepted when country=JM.

Example request

curl
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

json
{
  "ok": true,
  "customer_number": "SW460329",
  "user_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "email": "alice@example.com",
  "already_existed": false
}
FieldDescription
okAlways true on success.
customer_numberStable, auto-assigned identifier for this customer within the courier (e.g. SW460329). Use this when booking shipments.
user_idInternal UUID for the newly created user.
emailLowercased, normalised email as stored.
already_existedAlways false for a successful creation. Reserved for future idempotency behavior.

Get Customer Profile

GET/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
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
curl https://kanal.cyrusfieldtech.com/api/partner/v1/customers/SW460329 \
  -H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE"

Response — 200 OK

json
{
  "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"
}
FieldDescription
user_idUUID of the customer's Kanal login account. null if the customer has packages but no login.
emailCustomer's email address (lowercased).
nameCustomer's display name.
phonePhone number, or null if not provided.
customer_numberStable alphanumeric identifier (e.g. SW460329). Use this in shipment bookings.
countryISO-2 country code recorded at signup.
addressDefault shipping address, or null.
trnJamaica TRN (9 digits), or null. Returned unmasked to the issuing partner.
account_statusOne of active, pending_verification, blocked, or recipient_only (shipment record exists but no login account).
email_verifiedtrue once the customer has clicked the welcome email link.
created_atISO 8601 timestamp of account creation.

List Customer Packages

GET/partner/v1/customers/{identifier}/packages

Returns 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

ParamTypeDescription
limitintegerPage size — 1 to 100. Default: 20.
offsetintegerNumber of records to skip. Default: 0.
statusstringFilter to a specific status: pending · in_transit · ready_for_pickup · delivered · cancelled · return_to_sender

Example — all packages by customer number

curl
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
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

json
{
  "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
    }
  ]
}
FieldDescription
totalTotal count of matching packages (before pagination).
limit / offsetEcho of the pagination params supplied in the request.
items[].package_idInternal UUID of the package.
items[].tracking_numberKN-prefixed tracking code (e.g. KN1029384756). Use for public tracking at /c/{slug}/track/{number}.
items[].statusCurrent delivery status.
items[].shipment_methodair · sea · ground.
items[].weight_kgPackage weight in kilograms.
items[].sender_nameName of the US/origin sender or store.
items[].delivered_atISO 8601 delivery timestamp, or null if not yet delivered.

Get Shipping Rates

GET/partner/v1/rates

Returns 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
curl https://kanal.cyrusfieldtech.com/api/partner/v1/rates \
  -H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE"

Response — 200 OK

json
{
  "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 }
  ]
}
FieldDescription
currencyISO 4217 currency code for all prices in this response (e.g. JMD, USD).
per_lb_rateBase rate for 1 lb — convenience field matching rate_table[0].price.
rate_tableOrdered 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.
Rate lookup example (JavaScript):
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 table

Error Reference

All errors return JSON with a detail field. Standard HTTP status codes apply.

CodeStatusMeaning
400Bad RequestValidation failed — e.g., TRN provided without country=JM, or country code not exactly 2 letters.
401UnauthorizedMissing, malformed, or revoked API key.
402Payment RequiredThe owning courier's subscription invoice is overdue. Resolve outstanding invoices to re-enable API access.
409ConflictA customer with this email already exists in the courier's tenant.
422Unprocessable EntityRequest body failed Pydantic schema validation (invalid email format, missing required field, etc.).
429Too Many RequestsPer-tenant rate limit exceeded. Retry after the rolling window resets (~60 seconds).
500Internal Server ErrorUnexpected server error. Contact support if this persists.

409 — Duplicate email

json
{
  "detail": "Email already registered for this tenant"
}

401 — Invalid key

json
{
  "detail": "Invalid or revoked partner API key"
}

402 — Locked tenant

json
{
  "detail": "Owning tenant account is locked — resolve outstanding invoices to re-enable partner API access"
}

422 — Validation error

json
{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "value is not a valid email address",
      "type": "value_error.email"
    }
  ]
}

Getting an API Key

01

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.

02

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.

03

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.

04

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.