API · v2.0.0

Fleet API

For Max, Ulti and anyone else on the fleet: quotes, proposals, invoices, subscriptions and payment links across brands and Stripe accounts, without the UI.

Authentication

Create a token in Settings with a label (for example "Max") and scopes. It is shown once. Send it as a bearer header on every call. Every call is written to the audit log with the token label, endpoint, time and IP.

export KB_TOKEN=kb_...
curl https://billing.kyberapps.com/api/v1/me -H "Authorization: Bearer $KB_TOKEN"

Rate limit per token: 60 burst, then 1 request per second. 429 carries Retry-After. Idempotency-Key on POST replays the stored response for 1 key + token + path.

Scopes

readList and read everything, including proposal acceptance status.
quotes:writeCreate and update organizations and quotes; mark quotes sent.
proposals:writeCreate proposals from quotes, edit them, mark sent.
invoices:writeCreate Stripe or manual invoices, send, mark paid.
subscriptions:writeCreate subscriptions from accepted proposals, sync, set brand.
links:writeCreate and deactivate payment links.
adminEverything, including Stripe account sync.

Conventions

JSON bodies. Money is integer cents (unit_cents, total_cents); on quote lines you may send unit_price in dollars instead. Dates are YYYY-MM-DD. Timestamps are UTC ISO 8601. Errors are {"error": "..."} with honest status codes: 400, 401, 403, 404, 405, 409, 422, 429, 502, 503.

The whole flow in five calls

ORG=$(curl -s https://billing.kyberapps.com/api/v1/orgs -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" -d '{"name":"Sample Academy","type":"school","enrollment":900}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
Q=$(curl -s https://billing.kyberapps.com/api/v1/quotes -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" -d "{\"org_id\":$ORG,\"lines\":[{\"product\":\"KyberCopy\",\"qty\":1}]}" | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
curl -s -X POST https://billing.kyberapps.com/api/v1/quotes/$Q/send -H "Authorization: Bearer $KB_TOKEN"            # share_url for the customer
P=$(curl -s https://billing.kyberapps.com/api/v1/proposals -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" -d "{\"quote_id\":$Q,\"send\":true}" | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
curl -s https://billing.kyberapps.com/api/v1/proposals/$P/status -H "Authorization: Bearer $KB_TOKEN"              # poll until status=accepted
curl -s https://billing.kyberapps.com/api/v1/invoices -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" -d "{\"proposal_id\":$P,\"send\":true}"

Endpoints

GET/api/v1/meread

Who am I

Returns the token label and scopes.

curl https://billing.kyberapps.com/api/v1/me -H "Authorization: Bearer $KB_TOKEN"
GET/api/v1/pricing-rulesread

Pricing rules

The pricing rules as data: KyberCopy bands, KyberGate floor and bands, manual products, multi-year discounts.

curl https://billing.kyberapps.com/api/v1/pricing-rules -H "Authorization: Bearer $KB_TOKEN"
GET/api/v1/pricing-rules/suggest?product=KyberCopy&qty=1&enrollment=900read

Suggest a line

Default description, unit and unit price for a product. requires_price=true means the price must be supplied manually.

curl https://billing.kyberapps.com/api/v1/pricing-rules/suggest?product=KyberCopy&qty=1&enrollment=900 -H "Authorization: Bearer $KB_TOKEN"
GET/api/v1/termsread

Agreement text

Current Master Subscription Agreement text and version.

curl https://billing.kyberapps.com/api/v1/terms -H "Authorization: Bearer $KB_TOKEN"
GET/api/v1/orgs?search=read

List organizations

curl https://billing.kyberapps.com/api/v1/orgs?search= -H "Authorization: Bearer $KB_TOKEN"
POST/api/v1/orgsquotes:write

Create organization

type is school, district or other. contacts is a list of {name,email,phone,role}. products is a list of product names.

curl -X POST https://billing.kyberapps.com/api/v1/orgs \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name": "Sample Academy", "type": "school", "address": "1 Main St, Albany, NY", "enrollment": 900, "contacts": [{"name": "Dana Lee", "email": "dlee@sample.edu", "phone": "", "role": "IT Director"}], "products": ["KyberCopy"], "tax_exempt": true, "tax_exempt_no": "EX-12345"}'
GET/api/v1/orgs/{id}read

Get organization

Includes the org's quotes, proposals and invoices.

curl https://billing.kyberapps.com/api/v1/orgs/1 -H "Authorization: Bearer $KB_TOKEN"
PATCH/api/v1/orgs/{id}quotes:write

Update organization

Send only the fields you want to change.

curl -X PATCH https://billing.kyberapps.com/api/v1/orgs/1 \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"enrollment": 1200}'
GET/api/v1/quotes?org_id=&status=read

List quotes

status: draft, sent, accepted, expired, declined, converted.

curl https://billing.kyberapps.com/api/v1/quotes?org_id=&status= -H "Authorization: Bearer $KB_TOKEN"
POST/api/v1/quotesquotes:write

Create quote

Lines: product (KyberGate, KyberAccess, KyberWatch, KyberCopy), description (optional, defaulted), qty, unit, unit_cents or unit_price (dollars), years, term_start, term_end, is_pilot. KyberCopy price is defaulted from the org's enrollment band when unit_cents is omitted. KyberGate never below 500 cents. KyberAccess and KyberWatch need a manual price. discount_type: none, percent (discount_percent, e.g. 10) or fixed (discount_amount in dollars). multi_year_percent: 0, 10 or 15. valid_until defaults to 30 days.

curl -X POST https://billing.kyberapps.com/api/v1/quotes \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"org_id": 1, "lines": [{"product": "KyberCopy", "qty": 1, "term_start": "2026-11-01", "term_end": "2027-10-31"}, {"product": "KyberGate", "qty": 800, "unit_price": "5.50", "description": "KyberGate Basic, per device, annual"}], "discount_type": "percent", "discount_percent": 10, "notes": "Pricing held through the validity date."}'
GET/api/v1/quotes/{id}read

Get quote

Includes share_path (/q/<token>) for the customer-facing page.

curl https://billing.kyberapps.com/api/v1/quotes/1 -H "Authorization: Bearer $KB_TOKEN"
PATCH/api/v1/quotes/{id}quotes:write

Update quote

Same fields as create. Pass status: declined to decline. Accepted or converted quotes are locked.

curl -X PATCH https://billing.kyberapps.com/api/v1/quotes/1 \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"lines": [{"product": "KyberCopy", "qty": 2, "unit_price": "3600"}]}'
POST/api/v1/quotes/{id}/sendquotes:write

Mark quote sent

Sets status=sent and returns share_url. Nothing is emailed; deliver the link yourself.

curl -X POST https://billing.kyberapps.com/api/v1/quotes/1/send \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
POST/api/v1/quotes/{id}/duplicatequotes:write

Duplicate quote

New draft with a new number.

curl -X POST https://billing.kyberapps.com/api/v1/quotes/1/duplicate \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
GET/api/v1/proposals?org_id=&status=read

List proposals

curl https://billing.kyberapps.com/api/v1/proposals?org_id=&status= -H "Authorization: Bearer $KB_TOKEN"
POST/api/v1/proposalsproposals:write

Create proposal from quote

Snapshots the quote and org, drafts the cover letter, attaches the current agreement text. payment_terms: net30 (PO, Stripe send_invoice) or card (hosted invoice, charge automatically). send: true also marks it sent.

curl -X POST https://billing.kyberapps.com/api/v1/proposals \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"quote_id": 1, "payment_terms": "net30", "po_number": "", "send": true}'
GET/api/v1/proposals/{id}read

Get proposal

Includes share_url and the quote snapshot.

curl https://billing.kyberapps.com/api/v1/proposals/1 -H "Authorization: Bearer $KB_TOKEN"
GET/api/v1/proposals/{id}/statusread

Proposal status

Acceptance details: accepted_name, accepted_title, accepted_email, accepted_at (UTC), accepted_ip.

curl https://billing.kyberapps.com/api/v1/proposals/1/status -H "Authorization: Bearer $KB_TOKEN"
PATCH/api/v1/proposals/{id}proposals:write

Update proposal

cover_letter, payment_terms, po_number, term_start, term_end, status (sent/declined). Locked after acceptance.

curl -X PATCH https://billing.kyberapps.com/api/v1/proposals/1 \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"po_number": "PO-2026-118"}'
POST/api/v1/proposals/{id}/sendproposals:write

Mark proposal sent

Returns share_url. Nothing is emailed.

curl -X POST https://billing.kyberapps.com/api/v1/proposals/1/send \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
GET/api/v1/invoices?org_id=&status=read

List invoices

Also returns stripe_connected and stripe_test_mode.

curl https://billing.kyberapps.com/api/v1/invoices?org_id=&status= -H "Authorization: Bearer $KB_TOKEN"
POST/api/v1/invoicesinvoices:write

Create invoice from accepted proposal

With Stripe connected: creates a draft Stripe invoice from the proposal snapshot (send_invoice net-30 for PO, charge_automatically for card). send: true finalizes and sends it. Without Stripe, or for check/PO tracking, pass manual: true to create a local invoice record (amount defaults to the proposal total).

curl -X POST https://billing.kyberapps.com/api/v1/invoices \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"proposal_id": 1, "send": false}'
GET/api/v1/invoices/{id}read

Get invoice

curl https://billing.kyberapps.com/api/v1/invoices/1 -H "Authorization: Bearer $KB_TOKEN"
POST/api/v1/invoices/{id}/sendinvoices:write

Finalize and send Stripe invoice

curl -X POST https://billing.kyberapps.com/api/v1/invoices/1/send \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
POST/api/v1/invoices/{id}/mark-paidinvoices:write

Mark paid (check or PO)

method: check, ach, po, other. reference: check number or remittance id.

curl -X POST https://billing.kyberapps.com/api/v1/invoices/1/mark-paid \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"method": "check", "reference": "chk 10422"}'
GET/api/v1/subscriptions?org_id=&status=&brand=&account=read

List subscriptions

Annual plans mirrored from Stripe, per brand and per Stripe account. amount_cents is the annualised value; next_invoice_at is the renewal date.

curl https://billing.kyberapps.com/api/v1/subscriptions?org_id=&status=&brand=&account= -H "Authorization: Bearer $KB_TOKEN"
POST/api/v1/subscriptionssubscriptions:write

Create subscription from accepted proposal

The proposal's quote must have billing_mode=subscription (or pass it anyway to bill an accepted quote annually). Creates the Stripe subscription on the proposal's account (annual, N-year interval for multi-year lines), the first invoice follows Stripe's collection method. Nothing is emailed.

curl -X POST https://billing.kyberapps.com/api/v1/subscriptions \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"proposal_id": 1}'
GET/api/v1/subscriptions/{id}read

Get subscription

curl https://billing.kyberapps.com/api/v1/subscriptions/1 -H "Authorization: Bearer $KB_TOKEN"
PATCH/api/v1/subscriptions/{id}subscriptions:write

Set subscription brand

brand: kyberapps, kybergate, kyberaccess, kyberwatch, kybercopy or unassigned.

curl -X PATCH https://billing.kyberapps.com/api/v1/subscriptions/1 \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"brand": "kybergate"}'
POST/api/v1/subscriptions/{id}/syncsubscriptions:write

Sync subscription from Stripe

curl -X POST https://billing.kyberapps.com/api/v1/subscriptions/1/sync \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
GET/api/v1/payment-links?brand=&account=&org_id=read

List payment links

curl https://billing.kyberapps.com/api/v1/payment-links?brand=&account=&org_id= -H "Authorization: Bearer $KB_TOKEN"
POST/api/v1/payment-linkslinks:write

Create payment link

Stripe-hosted pay page. account (label) defaults to the brand's account. unit_cents or unit_price; qty; recurring: empty (one-off), month or year; product optional (KyberGate, KyberCopy, ...).

curl -X POST https://billing.kyberapps.com/api/v1/payment-links \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"brand": "kybergate", "description": "KyberGate \u00b7 1,200 devices \u00b7 2026-27", "unit_price": "6000", "qty": 1, "recurring": "", "org_id": 1}'
GET/api/v1/payment-links/{id}read

Get payment link

curl https://billing.kyberapps.com/api/v1/payment-links/1 -H "Authorization: Bearer $KB_TOKEN"
POST/api/v1/payment-links/{id}/deactivatelinks:write

Deactivate payment link

curl -X POST https://billing.kyberapps.com/api/v1/payment-links/1/deactivate \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
POST/api/v1/orgs/{id}/portalread

Customer portal session

Short-lived Stripe customer-portal URL for the org (invoice history, payment methods, billing details; cancel is off). account optional.

curl -X POST https://billing.kyberapps.com/api/v1/orgs/1/portal \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"account": "kyber-systems"}'
GET/api/v1/stripe-accountsread

List Stripe accounts

Label, Stripe account id, display name, default brand, mode, connected, webhook secret set, last sync. Never returns keys.

curl https://billing.kyberapps.com/api/v1/stripe-accounts -H "Authorization: Bearer $KB_TOKEN"
POST/api/v1/stripe-accounts/{id}/syncadmin

Sync one Stripe account

id is the label or numeric id. full: true also walks products and quotes (the Import existing action). Read-only against Stripe.

curl -X POST https://billing.kyberapps.com/api/v1/stripe-accounts/1/sync \
  -H "Authorization: Bearer $KB_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"full": false}'
GET/api/v1/brandsread

Brands

The five brands (KyberApps umbrella + four products), the payee, and which Stripe account each brand bills from.

curl https://billing.kyberapps.com/api/v1/brands -H "Authorization: Bearer $KB_TOKEN"
GET/api/v1/dashboard?brand=&account=read

Dashboard numbers

MRR, ARR, open, overdue, paid YTD, active subscriptions, pilots: total, per brand and per Stripe account.

curl https://billing.kyberapps.com/api/v1/dashboard?brand=&account= -H "Authorization: Bearer $KB_TOKEN"