https://middleware.bso-projects.com/partner. All calls send X-API-Key; the ones
marked verified also require X-Verified-Session (see Authentication).
Every response is a typed, documented schema — the same shapes are rendered in the
interactive Swagger. Internal fields
(Stripe IDs, operator notes, MAC, user_id) are never exposed.
Identify
Search customers
GET /agent/customers/search?q={term}&limit={n}
Matches contact name, email, phone, short account id, invoice address, building/unit and
service location. Returns masked matches only — enough to identify a caller, not enough to
leak PII before verification.
ambiguous is true (match_count > 1), ask the caller for their
service address or unit, append it to q, and search again — service_locations on each
candidate is the discriminator. Never auto-select a match; if it stays ambiguous, ask
another distinguishing detail or escalate.
Get a customer
GET /agent/customers/{customerId}
Full profile — contacts, invoice address, service locations, subscriptions. Requires a
verified session (verify the caller first). Returns 404 if the customer is not owned by your
provider (no cross-provider enumeration). Internal fields are never returned.
Verify
See Authentication forPOST /agent/verify/issue and
POST /agent/verify/check. verify/issue accepts channel: "email" (default) or "sms"
(SMS is delivered via Twilio and is inert until configured — returns 503
dependency_unconfigured otherwise).
Answer
Billing summary
GET /agent/customers/{customerId}/billing-summary
Payment history
GET /agent/customers/{customerId}/payments
The customer’s payments. (kurnl has no separate payments table — a paid invoice with its Stripe
payment-intent is the payment record — so this is derived from paid invoices.)
Network health
GET /agent/customers/{customerId}/network-health
Read-only per-subscription provisioning/activation status plus modem (CPE) status from the
ACS when available. No switch control, no live-switch queries.
Communication history
GET /agent/customers/{customerId}/tickets
The customer’s support tickets, read by their email on file.
Act
Preview a plan change
POST /agent/customers/{customerId}/plan-change/preview
Returns the exact billing impact — recurring change plus the one-time prorated adjustment
for the rest of this calendar month — without applying anything. Use it to explain the
cost before confirming.
Breaking change (September 2026):
one_time_adjustment may now come back null (see
the three cases below); its type value that used to read charge now reads
next_invoice_line. proration_factor (on the response) and due_in_days (inside
one_time_adjustment) are deprecated and will be removed in the next release —
read days / days_in_period instead (see below).one_time_adjustment is nullable. It comes back null, with an explanatory note
and a normal 200, in three cases:- the current calendar month has not been invoiced yet — the monthly invoice will simply
reflect the new price, so there is nothing to charge or credit today (
note: “This month has not been invoiced yet; the monthly invoice will reflect the new price.”) - an invoice exists for the month but its billing-document row is missing (
note: “invoice exists, billing document missing — reconcile”) - the billing-document row exists but the invoice record is missing (
note: “document exists, invoice record missing — reconcile”)
null, one_time_adjustment.type is one of:next_invoice_line— an upgrade. Nothing is charged today; the amount appears as a labelled line on the customer’s next monthly invoice.credit— a downgrade. The amount is credited now and applied to the next invoice.none— the correction rounds to less than $0.50 and is skipped rather than issued;amountis0.00. It isn’t lost — it’s folded into whichever correction runs next.
days / days_in_period replace the old proration_factor: the billing period is the
calendar month, so the adjustment is price difference x days / days in that month. There
is no due_in_days any more — an upgrade is never billed on its own; it always rides the
next monthly invoice. A second plan change on the same calendar day is refused with
409 — a change day is billed at the new price, so the next change is possible from
tomorrow; only a kurnl platform admin can override that, and the AI cannot.Apply a plan change
POST /agent/customers/{customerId}/plan-change
Applies the change. Requires confirm: true — call it only after the customer has seen
the preview and agreed. Proration is handled automatically.
Plan-change apply is live wherever your key has access — there is no sandbox gate: a
call with
confirm: true applies a real, billing-affecting change in production. Use it
deliberately. Planned-but-not-shipped hardening: a signed quote_token (preview→apply),
an Idempotency-Key, and a change-confirmation/rollback flow (all planned, none shipped). Validate the read +
verify + escalate loop first.confirm: true the call returns 400 confirmation_required.
Update contact fields
PATCH /agent/customers/{customerId}/contact
Update the customer’s approved contract or invoice contact fields. Benign write — no billing or
network impact. Applies immediately — there is no confirmation email or rollback yet
(see Change confirmation & rollback).
contact_type:contract(default) |invoice. Only the fields you send are changed.
Password reset / resend activation
POST /agent/customers/{customerId}/password-reset
Emails the customer a self-service portal link — a password-reset link (15-min token) or a
re-sent account set-up link (14-day token; the provider’s “Account setup email” template).
The customer completes it themselves.
action:password_reset(default) |resend_activation.resend_activationonly applies while the customer has not yet chosen a password; afterwards it answers 409already_activated— send apassword_resetinstead.
Resend an invoice
POST /agent/customers/{customerId}/invoices/{invoiceId}/resend
Emails a copy of the invoice to the customer.
Add a support note
POST /agent/customers/{customerId}/notes
Append an internal, operator-visible note to the customer’s record, attributed to the AI agent.
Never shown to the customer.
Escalation
Create a ticket
POST /agent/tickets
Opens an enriched support ticket routed to the provider’s support group. Not
verification-gated, so it works even when the caller can’t be verified — the primary escalation
path on repeated auth failure or any downstream error. Provide customer_id (must be your
provider’s) or a contact_email.
priority:low|normal|high.ai_summaryandai_diagnosisgo into the customer-facing article;transcriptis stored as an internal note.
Change confirmation & rollback
Rate limits
Exceeding a limit returns
429 (standard rate-limit response; not the error envelope below).
Back off and retry.
Errors
Most/agent errors return a normalized envelope with a stable, machine-readable code
and a request_id you can quote to support — so your agent branches on the code, not the message:
Recommended default on downstream (502/503) and server (500) errors: graceful handover —
create an escalation ticket and offer a human callback rather than retrying. Back off on
429, re-verify on session errors. These behaviours are configurable to your agent design.