When to use this

Use CKO-04 when:
  • Your customers buy on your own website — your funnel, your cart, your payment provider
  • You are an incumbent or existing ISP bringing your own subscribers to the kurnl network
  • You handle your own billing entirely — no Stripe or kurnl invoices needed
  • You want to provision subscribers programmatically from your own back-office system without a marketplace flow
You never create a kurnl checkout session on this path. Sessions belong to marketplace-originated checkout (CKO-03), where kurnl resolves the address and hands the shopper to you. CKO-04 is the simplest integration path if you already have subscriber relationships and just need kurnl to activate the network port.

How it works

Three calls from your storefront, then one to provision:
The last call does everything kurnl-side:
  1. Creates the subscriber record in kurnl (or reuses an existing account by email)
  2. Creates the subscription
  3. Either provisions the switch port via SSH (VLAN, bandwidth, optional MAC binding), or raises an install job for a technician — decided by the building’s line type, not by you. The response’s route says which.
There is no marketplace redirect, no Stripe checkout, and no webhook required on your side (though you can configure one to receive provisioning status).
A back-office migration where you already hold port mappings skips the first three calls and sends a location_hash directly — see Naming the premise below.

Making the call

Successful response — 200 OK:
On the install route job_id is null, route is "install", and the subscription is PENDING_INSTALL until a technician calls POST /provisioning/confirm-install. The port is provisioned asynchronously. Poll the job or wait for the provisioning.completed webhook. Retries are safe (idempotent). A repeat call for the same (subscriber email, plan version, location) triple returns 200 with the existing subscription instead of creating a duplicate — message is "Provisioning already in progress" (a job is still running) or "Subscription already exists (idempotent replay)", and job_id may be an empty string on a replay, so treat it as optional. The same subscriber + plan at a different location provisions a second line normally. A SUSPENDED subscription counts as existing. Rate limit: 120 requests/minute per API key.

Errors

Naming the premise

Every order has to say where. There are two ways, and which one you can use depends on what you know about the customer.

A unit — for a storefront (delivery_mode: "home_drop")

A shopper typing their address into your site has no way to know their switch port, and neither do you. Ask them for their address and suite instead:
  1. POST /serviceability/check with the address → a building_id.
  2. GET /serviceability/units?building_id=… → the suites, each with available.
  3. POST /provisioning/provider-initiated with delivery_mode: "home_drop" and the chosen unit_id.
Render unavailable suites disabled rather than hiding them, and trust available rather than anything else you may have cached about a unit.

A port — for a migration (delivery_mode: "direct", the default)

A location_hash identifies a physical switch port. You get one of two ways:
  1. From your provisioning records — if you mapped your subscriber addresses to kurnl location hashes during onboarding
  2. From the kurnl dashboard — look up the port in Dashboard → Network and copy the location hash
For large migrations, use Bulk Provisioning to supply a CSV of addresses and location hashes.
The serviceability endpoints never return a location_hash, and no endpoint does. A port is knowable only from your own records, the dashboard, or a device already detected on the network — so an anonymous shopper on your site is always a home_drop order. This is the single most common wrong turn on this integration.

Provision or install — the building decides

delivery_mode states what you have. The building’s line type decides what happens, and a request that does not fit it is refused with 422 rather than half-completed: Read route in the response (provision or install) and branch your confirmation screen on it — never on the presence of job_id, which is also empty on an idempotent replay.

No Stripe / no kurnl invoices

CKO-04 does not create any Stripe payment records or Invoice Ninja invoices. Billing is entirely your responsibility. kurnl tracks the subscription for network management purposes (deprovisioning on cancellation, reporting), but does not bill the subscriber on your behalf.
If you want kurnl to handle subscriber billing (invoices, Stripe payment links, self-service portal), use CKO-01 (the kurnl marketplace checkout) or CKO-03 instead.

Cancellation

When a subscriber’s service ends, call the subscription cancel endpoint so kurnl can deprovision the port:
This marks the subscription CANCELLED and sets canceled_at and ended_at (the response echoes both). ended_at is exclusive — service is off on that day. There is no minimum term: any date from today on is accepted. The port itself is not reset at call time: the deprovisioner resets it to the captive-portal VLAN once ended_at is reached. Service stays up until then. GET /api/v1/subscription/{subscription_id}/cancel/preview?end_date=YYYY-MM-DD (same JWT) is a read-only quote of what a cancellation on that date would record — the effective date, the calendar month it lands in, and, for kurnl-billed subscriptions, whether the month has already been invoiced (a credit note for the unused days plus every open credit swept into the refund) or has not (a final invoice for the days served, minus open credits). CKO-04 subscriptions are not billed by kurnl, so the money lines are always empty for them.
The cancellation endpoint is on the internal API surface (/api/v1) and requires dashboard JWT authentication, not an API key. If you need programmatic cancellation from your back-office system, use a service account JWT obtained from the dashboard login flow. Alternatively, cancellations can be initiated from Dashboard → Subscriptions.

Field reference