These endpoints are called on behalf of a subscriber, not by the ISP. They require a subscriber JWT obtained through your authentication flow, not your provider API key.

Authentication

Authorization: Bearer <subscriber_jwt> — the JWT issued to the subscriber after login.

Account holder and authorized users

An account has one account holder (the person who signed up and is named on the agreement) and up to five authorized users, each with their own login. Both see the same account; every endpoint here works for both unless it is marked holder only. A holder-only endpoint answers 403 with {"detail": {"code": "holder_only", "message": "Only the account holder can do this."}} to an authorized user. Holder only: changing the invoice contact, adding or removing authorized users, cancelling, and everything that pays — payment method, AutoPay, billing day, paying an invoice, the paper-billing request tickets — plus a PIPEDA deletion request. Preferences, the second factor and /me/contact are the signed-in person’s own.

Endpoints

GET /partner/subscriber/me

Returns the subscriber record, all their subscriptions, any anonymous external-checkout sessions linked to the account, and provider branding:
An account with no subscriber record yet (freshly registered, nothing ordered) gets a 200 with "subscriber": null and empty subscriptions — not a 404.

GET /partner/subscriber/me/reactivation

The account’s most recent cancelled subscription and whether it can be brought back — read-only, for every account user. case is withdraw while the cancellation has not taken effect yet (the line still runs until cancellation.ended_at — midnight in the provider’s time zone), reactivate once the line was switched off, resume when the line was never live (the order was cancelled before its install), null when nothing is cancelled. eligible is false whenever blockers names a reason (unpaid invoices, the suite served by someone else since, a line billed by the provider directly, …). path says what a reactivation takes: remote (the port comes back within minutes), technician (a visit is booked), none for a withdrawal. holder_only is true for an authorized user — the actions themselves are the account holder’s.
billing.first_charge is the prorated rest of the current calendar period from today (net, before tax) for a reactivation, null for a withdrawal and for a resume; billing_starts is now for a reactivation and activation for a resume (nothing is charged until the line is on) — except on the activate path (planning 152: a never-installed line on a first-line building), where billing_starts is now and first_charge is the first invoice’s estimate (setup fee plus the first month); monthly_price is the line’s own price, never the catalogue’s.

POST /partner/subscriber/me/subscriptions//reactivation

The action behind the preview, for the account holder only (holder_only 403 for an authorized user). The preview’s case decides what it does. case: "withdraw" — takes the pending cancellation back: the line returns to the status it had before the cancellation (status), its billing continues, any refund credit that was still open is voided and days the cancellation took back are invoiced again (billing summarises what was done).
case: "reactivate", path: "remote" — brings an ended line back on the same row: the switch port is provisioned first (VLAN, bandwidth, the device’s MAC), and only on a confirmed write the line goes ACTIVE again on its suite, its recurring billing resumes and the rest of the current period from today is invoiced (first_charge, net before tax, charged to the payment method on file through the usual invoice path). The call waits for the port — allow about a minute.
case: "reactivate", path: "technician" — a second- or third-line suite, or a first-line suite whose port no longer resolves: somebody has to go. The call schedules the visit rather than reactivating: the line goes back to PENDING_INSTALL on its own plan, price and payment method, its suite is held again, and a second install job of kind: "reinstall" is opened and booked the way a fresh order is — booking: "customer" on a first- or third-line building (the customer receives the booking e-mail), "operator" on a second-line one (the dispatch is scheduled by the operator). Nothing is charged now. The line goes ACTIVE — and its recurring billing resumes, with the rest of that month invoiced from the visit day — once the technician has been (the CPE’s first configuration pull, or the completed dispatch). The customer gets the reactivation e-mail at that point.
A subscription may therefore carry more than one install job — its completed first install and the reinstall. At most one is open at a time; install_jobs.kind is install or reinstall. case: "resume", path: "activate" — the order was cancelled before its install on a first-line building whose suite’s port resolves (planning 152): the first order finally happens now. The first and recurring invoices are created for the row, the balance is charged to the card on file (a decline answers 409 card_declined with the documents deleted and nothing else touched; no card → 409 no_payment_method), the port is provisioned without the reactivation port guard, the line goes ACTIVE on its suite and the welcome mail goes out. A port that will not switch on after the money moved leaves the line active and answers provisioned: false (the provider has the ticket). A first-line suite whose port does not resolve is refused: “This suite is not wired yet — contact your provider.”
case: "resume" — the order was cancelled before its install (by the customer, the operator, or the stale-install clock — cancellation_reason: "install_timeout") on a second- or third-line building. The call picks the order up again on the same row, plan, price and payment method: the line goes back to PENDING_INSTALL, its suite is held again, a fresh install job of kind: "install" is opened and handed to the fresh-order tail — booking: "customer" on a third-line building (the booking e-mail goes out), "operator" on a second-line one (the router is shipped from the install queue) — and the provider’s install ticket is raised as at checkout. Nothing is charged now. The line goes ACTIVE and is billed as a first order once the install is done. A never-installed order on a first-line building is not resumable (eligible: false, “never activated — ask your provider”); one that ended through a provider switch is not either. requested_start_date is kept while it is still ahead, otherwise reset to today.
When the eligibility says no, the answer is 409 with the reasons in the customer’s words:
port_in_use (another device is on the port) and provisioning_failed (the switch write did not confirm) also raise an operator ticket; nothing is changed on the line in either case. install_unavailable means the reinstall job could not be opened — the line is left as it was.

GET /partner/subscriber/me/details

Returns the full account profile used by the Account page. customer_number is the provider-scoped, human-readable customer number (seven random digits with a dash after the fourth, e.g. 0482-117 — leading zeros are part of the number, so treat it as a string; also printed on invoices); it is null only while no number has been allocated yet:
contract_contact is the account holder — the person named on the agreement, whom the provider names the customer. invoice_contact is the billing contact: the same person unless a separate billing contact was set (by the provider, or by the holder through PATCH /me/invoice-contact), which billing_contact_differs reports (compared by name, e-mail, phone and company, not by row). user.contact is the signed-in person (the holder’s contract contact, or the authorized user’s own record). A user with no subscriber record gets a 404 (unlike /me, which returns "subscriber": null).

PATCH /partner/subscriber/me/invoice-contact

Holder only. Sets a separate billing contact, the person the invoices and billing messages go to. All fields are optional strings: a field you leave out keeps its current value, an empty string clears it.
When the invoices still go to the account holder, this splits a separate billing contact off. Fields you leave out start from the holder’s details, and the holder’s own details are never changed. A separate billing contact must have an e-mail address and a name or a company, and it must be a different person from the holder. Response (200): { "success": true, "contact": { …the billing contact… }, "state": "separate" } Errors, as {"detail": {"code", "message"}}: When the billing e-mail address changes, the old and the new address each receive an information mail. After the change, kurnl also updates the billing contact on the provider’s Invoice Ninja client, if there is one. If that update fails, the change still stands and the response is still 200.

POST /partner/subscriber/me/invoice-contact/use-holder

Holder only. Sends the invoices to the account holder again. The separate billing contact is removed, and invoice_contact is then the holder. No request body. Response (200): { "success": true, "contact": { …the holder… }, "state": "shared" }. A 409 not_separate means the invoices already go to the holder. When the e-mail address changes, both addresses are told, as with the PATCH.

PATCH /partner/subscriber/me/contact

The signed-in person’s own details — for the holder the contract contact, for an authorized user their own record. All fields optional; there is no email, because the e-mail is the login itself. The holder’s edit also updates the billing contact while it describes the same person; a billing contact the provider split off is left alone, so a customer can never create a split from the portal.
Response: {"success": true, "contact": { "firstname", "lastname", "email", "phonenumber", "company" }}.

Authorized users

GET /partner/subscriber/me/authorized-users:
POST /partner/subscriber/me/authorized-users (holder only) with {"firstname", "lastname", "email", "phonenumber"?} creates the person’s login and e-mails them a set-up link valid for 14 days (the provider’s Authorized user invitation template). Returns 201 with the row plus invitation_sent. 409 with a code of email_has_portal_account (the e-mail already signs in somewhere) or limit_reached; 422 for a missing or invalid e-mail. POST …/{id}/resend-invite (holder only) sends the link again while the person has not set a password; 409 already_activated afterwards. DELETE …/{id} (holder only) removes the login and the person, 204; 404 for an id that is not one of this account’s authorized users.

PATCH /partner/subscriber/me/password

Response: {"success": true, "message": "Password updated successfully"}. A wrong current password returns 400 "Current password is incorrect".

GET /partner/subscriber/me/invoices

Returns the subscriber’s kurnl invoices (local invoice rows, not Invoice Ninja objects), sorted by invoice_date descending:
Returns 404 "No subscriber record found for this account" when the account has no subscriber record (same for the other /me/* sub-resources).

GET /partner/subscriber/me/credit-notes

Returns the subscriber’s credit notes — cancellation refunds and credits, plan-change and operator credits — read live from the provider’s billing system (Invoice Ninja), newest first. Drafts, archived and voided notes are never listed. Unlike invoices, nothing is stored on the kurnl side.
state is the customer-facing status: open (issued, not yet settled), refunded (refunded to the card), paid_out (paid out by other means), applied (applied against an invoice). amount is the gross amount in the provider’s currency. The PDF of a credit note is served by GET /api/v1/credit-notes/{id}/pdf with the subscriber’s session — the same access model as invoice PDFs; a note that does not belong to the subscriber answers 404. Returns 404 when the account has no subscriber record and 502 when the billing system is unreachable (never an empty list in that case).

GET /partner/subscriber/me/usage

Per-month download/upload usage in GB for the subscriber’s active subscription, oldest month first: every month from the one the subscription started in to the current month to date, at most 60 months. Without a start date, the last 6 complete calendar months plus the current month. Each month is {"month": "YYYY-MM", "download_gb": 12.34, "upload_gb": 5.67}. status is "ok" when any month carries traffic; "no_data" when every month reads zero (the months are still listed) or the line has no switch port yet ("months": []); "no_subscription" with "months": [] when the account has no subscription.

GET /partner/subscriber/status-history

The subscriber’s own connection timeline, newest first: {"events": [{"id", "started_at", "resolved_at", "ongoing"}]}. Degrades to an empty list on any failure so it stays loadable during outages.

PATCH /partner/subscriber/me/billing-day

Choose the day of the calendar month one of the subscriber’s own subscriptions is invoiced and charged on. Allowed values are 1 to 10; the default is 1.
  • effective_period is the first calendar month the new day applies to, as its 1st.
  • next_send_date is when the recurring invoice will next send, or null when the subscription has no recurring invoice yet.
The billing period is always the calendar month, so this changes only when money is collected — never what is owed. No invoice, no credit note and no correction is ever produced by a billing-day change. If the current month has already been invoiced, the new day takes effect from the next month instead.
Status codes: 200 on success · 404 when the subscription is not the caller’s · 422 when billing_day is outside 1–10 · 409 when the current month has not been invoiced yet and the chosen day has already passed (or is today) — pick a day still ahead of today, or wait until this month’s invoice is issued · 502 when the day was saved but the next invoice could not be rescheduled (retry).

GET/POST /partner/subscriber/me/payment-method*

GET .../me/payment-method, POST .../me/payment-method/setup and POST .../me/payment-method/confirm manage the subscriber’s saved card in their provider’s own Stripe account. If that provider has no Stripe connection, each returns 409:
Subscriber management for ISPs (listing subscribers, looking up accounts, triggering cancel) is done through the dashboard, not the partner API. Subscribers are created automatically as part of the provisioning flows — see External Checkout and Provider-Initiated Provisioning.

Ordering service at another address (planning 151)

A signed-in customer orders a new line on their own account — a mover, or a customer whose last line ended and whose suite is now served by someone else. Three holder-only routes, keyed on the token’s subscriber; the provider is the portal’s (a live line’s provider first, else the newest). The marketplace’s “that e-mail already has an account” does not apply here: the account is known.

GET /partner/subscriber/me/orders/options?building_id=

served is false when the provider does not cover the building or the building cannot take an order this way (unclassified, no suites) — the portal then shows the marketplace link. route is how a fresh order there is served: install (second/third line — a technician or a shipped router) or provision (a first-line building, planning 152: the suite’s port is provisioned on payment; the order charges the card on file at once and answers route: "provision", first_charge, booking: null; a declined card → 409 card_declined). A suite is available when it is free and no live order references it. current_subscription is the running line a mover may end on the move-in date. postal_unit (additive) is the suite label the customer’s postal address uses when it differs from unit_number, the unit id; null when the unit carries no separate label. Show it to the customer in place of unit_number when set; the id stays the key.

GET /partner/subscriber/me/orders/preview?building_id=&plan_version_id=&unit_id=&requested_start_date=&end_current_subscription_id=

A dry run of the order’s preparation: what the order core would refuse (a held suite, an unknown plan, the order window) comes back as a blockers sentence, never as an error; a missing card on file is a blocker too. Nothing is charged on the install route (first_charge null, billing_starts: activation); the promo is the plan version’s live campaign.

POST /partner/subscriber/me/orders

Runs the same order core as the marketplace checkout with payment: saved_method (no hosted session — the card on file is charged once the install is done) and source: portal; the row lands PENDING_INSTALL with its install job, the provider’s install ticket and — on a third-line building — the booking invitation. With end_current_subscription_id the running line is cancelled for the move-in date after the order stands (reason moving); a failed order ends nothing. Audit subscription.ordered_from_portal.
409 {code, message}: no_payment_method (add a card first), not_served (another provider’s plan), suite_required, suite_mismatch, not_running (the line to end is not this account’s running line), no_provider. The core’s own refusals keep their status: 404 plan or suite, 409 held suite, 422 order window.