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.

List Customer Invoices

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

Returns a paginated list of invoices for the given 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.
offsetintegerRecords to skip. Default: 0.
statusstringunpaid · paid · overdue · cancelled

Example — all invoices by customer number

curl
curl "https://kanal.cyrusfieldtech.com/api/partner/v1/customers/SW460329/invoices?limit=10&offset=0" \
  -H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE"

Example — unpaid invoices by email

curl
curl "https://kanal.cyrusfieldtech.com/api/partner/v1/customers/alice%40example.com/invoices?status=unpaid" \
  -H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE"

Response — 200 OK

json
{
  "total": 2,
  "limit": 10,
  "offset": 0,
  "items": [
    {
      "invoice_id": "b2c3d4e5-0001-0000-0000-000000000001",
      "invoice_number": "INV-00042",
      "customer_name": "Alice Brown",
      "customer_email": "alice@example.com",
      "amount": 1750.00,
      "currency": "JMD",
      "status": "unpaid",
      "due_date": "2026-09-20T00:00:00Z",
      "paid_at": null,
      "payment_method": null,
      "package_id": "a1b2c3d4-0001-0000-0000-000000000001",
      "notes": "Air shipment 2 lbs",
      "created_at": "2026-09-07T10:00:00Z"
    },
    {
      "invoice_id": "b2c3d4e5-0002-0000-0000-000000000002",
      "invoice_number": "INV-00038",
      "customer_name": "Alice Brown",
      "customer_email": "alice@example.com",
      "amount": 3850.00,
      "currency": "JMD",
      "status": "paid",
      "due_date": "2026-08-15T00:00:00Z",
      "paid_at": "2026-08-12T14:30:00Z",
      "payment_method": "wipay",
      "package_id": "a1b2c3d4-0002-0000-0000-000000000002",
      "notes": "Air shipment 5 lbs",
      "created_at": "2026-08-08T09:00:00Z"
    }
  ]
}
FieldDescription
invoice_idInternal UUID of the invoice.
invoice_numberHuman-readable reference displayed on the invoice PDF (e.g. INV-00042).
customer_nameName as recorded on the invoice.
customer_emailEmail the invoice is addressed to.
amountTotal invoice amount in the stated currency.
currencyISO 4217 currency code (e.g. JMD, USD).
statusOne of unpaid · paid · overdue · cancelled.
due_dateISO 8601 payment due date, or null.
paid_atISO 8601 timestamp when payment was confirmed, or null.
payment_methode.g. wipay · stripe · cash · bank_transfer, or null if unpaid.
package_idUUID of the linked package, or null for standalone invoices.
notesFree-text note added by the dispatcher, or null.
created_atISO 8601 invoice creation timestamp.

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

Add Newsletter Subscriber

POST/partner/v1/subscribers

Adds an email address to the courier's newsletter subscriber list (visible in the Communication → Subscribers tab). Idempotent — re-submitting an address that was previously unsubscribed will reactivate it; submitting an already-active address returns ok: true silently.

Request body

FieldTypeRequiredDescription
emailstringYesValid email address. Stored in lowercase.
namestringNoSubscriber's display name. Shown in the dashboard.
sourcestringNoOrigin label (e.g. "kyoown_platform", "mobile_app"). Defaults to "partner".

Example request

curl
curl -X POST https://kanal.cyrusfieldtech.com/api/partner/v1/subscribers \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE" \
  -d '{
    "email": "alice@example.com",
    "name": "Alice Brown",
    "source": "kyoown_platform"
  }'

Response — 201 Created (new subscriber)

json
{
  "ok": true,
  "resubscribed": false
}

Response — 201 (reactivated unsubscribed email)

json
{
  "ok": true,
  "resubscribed": true
}

Unsubscribe an Email

DELETE/partner/v1/subscribers/{email}

Marks a subscriber as inactive so they no longer appear in the active subscriber list and are excluded from future campaigns. Idempotent — calling on a non-existent or already-inactive address returns ok: true silently. URL-encode the email address (replace @ with %40).

Example request

curl
curl -X DELETE "https://kanal.cyrusfieldtech.com/api/partner/v1/subscribers/alice%40example.com" \
  -H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE"

Response — 200 OK

json
{
  "ok": true,
  "unsubscribed": true
}
FieldDescription
okAlways true when the request was well-formed and authorised.
unsubscribedtrue if the record was just deactivated; false if it was already inactive or not found.
reasonOmitted on success. "already_inactive" or "not_found" when unsubscribed is false.

Create a Courier Tenant

POST/partner/v1/tenants

Registers a new courier company (tenant) and provisions its first company_admin account. Designed for platforms that onboard couriers externally (e.g. KYoown) and need a matching Kanal tenant created immediately after their own signup flow. A verification email is dispatched to the admin's inbox — the tenant can log in once the email address is confirmed.

Note: No subscription invoice is generated for partner-created tenants — billing is handled by the calling platform. The tenant account is active immediately (not locked) so staff can log in as soon as they verify their email.

Request body

FieldTypeRequiredDescription
company_namestringYesDisplay name for the courier company. Must be unique across the platform.
admin_namestringYesFull name of the company admin account to provision.
admin_emailstringYesEmail address for the admin account. Must not already exist on the platform.
admin_passwordstringYesInitial password for the admin account. Minimum 6 characters.
phonestringNoCompany contact phone number.
addressstringNoCompany street address.
countrystringNoISO 3166-1 alpha-2 country code. Defaults to "JM" (Jamaica).
planstringNoSubscription plan: "starter", "pro", or "scale". Defaults to "starter".
websitestringNoCompany website URL.

Example request

curl
curl -X POST https://kanal.cyrusfieldtech.com/api/partner/v1/tenants \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer kanal_pk_YOUR_KEY_HERE" \
  -d '{
    "company_name": "KYo Couriers Ltd",
    "admin_name": "Marcus Reid",
    "admin_email": "marcus@kyocouriers.com",
    "admin_password": "secure-temp-pass",
    "phone": "+1-876-555-0100",
    "address": "12 Harbour St, Kingston",
    "country": "JM",
    "plan": "starter"
  }'

Response — 201 Created

json
{
  "ok": true,
  "company_id": "a1b2c3d4-...",
  "company_name": "KYo Couriers Ltd",
  "admin_email": "marcus@kyocouriers.com",
  "admin_user_id": "u1u2u3u4-...",
  "public_slug": "ky-7f3a12",
  "role": "company_admin",
  "account_locked": false,
  "email_verified": false,
  "verification_required": true,
  "customer_signup_url": "https://kanal.cyrusfieldtech.com/c/ky-7f3a12/signup",
  "created_at": "2026-10-02T00:00:00Z",
  "message": "Tenant created. The admin must verify their email before logging in."
}
FieldDescription
company_idUUID of the newly created tenant company.
admin_user_idUUID of the provisioned company_admin user account.
public_slugURL-safe slug for the tenant's public customer signup page.
customer_signup_urlFull URL customers can use to self-register with this courier.
verification_requiredAlways true. The admin must click the verification link in their inbox before logging in.

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.